Back Press Handling — је механизам пресретања и обраде системског дугмета „Назад” у Android-у, који одређује која се радња извршава при његовом притиску. Према Android Developers (2024), од Android 11 традиционални метод onBackPressed() замењен је са OnBackPressedDispatcher. Нови API омогућава компонентама на било ком нивоу хијерархије да пресретну притисак, а не само Activity. Кључна разлика — подршка за вишеструке callback-ове у ланцу са приоритетима.
Главно
Back Press Handling — је системски механизам Android-а који одређује шта се дешава када корисник притисне хардверско или софтверско дугме „Назад”. У зависности од контекста, притисак може: затворити тренутни екран и вратити се на претходни, сакрити тастатуру, затворити Drawer или Bottom Sheet, изаћи из апликације ако се корисник налази на кореном екрану.
Понашање дугмета „Назад” еволуирало је са сваком верзијом Android-а. У Android 10 појавили су се гестови навигације, у Android 11 — OnBackPressedDispatcher као стандардни API, а у Android 13 — побољшана подршка за predictive back gesture, где систем приказује анимацију прелаза пре стварног извршења радње. Google се доследно креће ка предвидљивом и конзистентном понашању Back Press-а на свим уређајима.
Правилна обрада дугмета „Назад” — критични елемент UX у Android апликацији. Корисник очекује да га притисак „Назад” враћа на претходни екран у стеку навигације, а не да затвара апликацију изненада. Кршење овог очекивања је један од главних разлога негативних рецензија и ниских оцена у Google Play-ју.
Историја Back Press API-ја у Android-у одражава општу еволуцију платформе: од једноставног метода у Activity-ју до флексибилног система callback-ова са подршком за lifecycle и Compose. Размотримо три фазе развоја.
Од првог Android API-ја дугме „Назад” се обрађивало у методу onBackPressed класе Activity. Програмер је превазилазио овај метод и писао своју логику. Проблем је био у томе што Fragment и View нису могли да пресретну притисак — све управљање је ишло преко Activity-ја. То је доводило до надувених Activity-ја и сложеног ланца if-else за одређивање ко треба да обради притисак.
Са Activity 1.0.0 (AndroidX) Google је представио OnBackPressedDispatcher. Ово је централни диспечер који прима callback-ове од било које компоненте — Activity, Fragment, Dialog, прилагођеног View-а. Callback-ови се региструју са одређивањем редоследа (путем priority) и могу се динамички додавати или уклањати. OnBackPressedDispatcher се позива раније од старог onBackPressed-а, што омогућава пресретање притиска пре него што га Activity обради.
Android 13 је увео predictive back gesture — системску анимацију која показује куда води притисак „Назад”, још пре него што корисник заврши гест. За подршку овој анимацији, програмери морају да користе OnBackPressedDispatcher и да назначе да ли callback подржава системску анимацију кроз својство isEnabled. Ако callback не подржава predictive animation, систем приказује подразумевану анимацију која можда не одговара контексту апликације.
| API | Минимални SDK | Подршка за Fragment | Predictive Back |
|---|---|---|---|
| onBackPressed | API Level 1 | Преко Activity-ја | Не |
| OnBackPressedDispatcher | Activity 1.0.0 | Директна | Делимична |
| OnBackPressedDispatcher + lifecycle | Activity 1.3.0 | Lifecycle-aware | Потпуна |
OnBackPressedDispatcher — је језгро новог Back Press API-ја. Он управља ланцем callback-ова, позивајући их по реду до првог који обради догађај. Ако ниједан callback не обради притисак, диспечер извршава подразумевану радњу — позив finish() за Activity или popBackStack() за Navigation Component.
Callback се региструје кроз addCallback са навођењем LifecycleOwner-а и објекта OnBackPressedCallback. Callback садржи својство isEnabled — ако је постављено на false, callback се прескаче. За priority се може проследити вредност од 0 (најнижи) до Integer.MAX_VALUE. Fragment Activity Result API користи овај механизам за аутоматску регистрацију callback-ова везаних за животни циклус.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(enabled = true) {
override fun handleOnBackPressed() {
if (isDrawerOpen) { closeDrawer() }
else { isEnabled = false; onBackPressed() }
}
}
onBackPressedDispatcher.addCallback(this, callback)
}
Callback се аутоматски уклања када LifecycleOwner пређе у стање DESTROYED. Ово решава стари проблем цурења callback-а при ротацији екрана. Ако је callback додат у Fragment, гарантовано ће бити уклоњен при уништењу Fragment-а. За привремено искључивање callback-а користи се својство isEnabled — може се пребацивати без уклањања и поновне регистрације.
Редослед позивања је обрнут редоследу додавања: последњи додати callback добија контролу први. Ово је логично јер најдубље угнежђени UI елемент (нпр. Bottom Sheet унутар Fragment-а) треба да обради притисак раније него родитељски Fragment. Ако најдубљи callback не обрађује притисак (isEnabled = false), контрола се преноси следећем у ланцу.
Fragment API обезбеђује сопствену интеграцију са OnBackPressedDispatcher-ом кроз метод requireActivity().onBackPressedDispatcher. Почевши од Fragment 1.2.0, сваки Fragment може да региструје свој callback који се аутоматски везује за животни циклус Fragment-а и уклања се при његовом уништењу.
Регистрација callback-а у Fragment-у се врши у onCreate, onViewCreated или чак у самом View-у — важно је само да LifecycleOwner (Fragment) буде активан. Када Fragment пређе у стање STARTED, callback се укључује, при STOPPED — искључује. Ово гарантује да скривени Fragment (у ViewPager-у) неће обрађивати притисак „Назад”.
class EditorFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showDiscardDialog()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
}
Дијалози и BottomSheet аутоматски пресрећу притисак „Назад” за затварање. Ако треба да извршите додатну радњу пре затварања — региструјте callback са вишим приоритетом. Важно: ако сте поставили setCancelable(false) на Dialog, callback неће радити — ово је системско понашање.
Jetpack Compose обезбеђује декларативни API за обраду дугмета „Назад” кроз composable функцију BackHandler. BackHandler прима enabled (булову вредност) и onBack — callback који се позива при притиску. Ако је enabled = false, притисак се прослеђује даље у ланцу.
BackHandler аутоматски региструје OnBackPressedCallback у OnBackPressedDispatcher-у родитељског Activity-ја. Он узима у обзир животни циклус composable-а: при изласку из композиције callback се уклања. enabled може бити везан за стање — на пример, приказивати дијалог потврде само ако у форми постоје несачуване измене.
@Composable
fun EditScreen(hasUnsavedChanges: Boolean) {
BackHandler(enabled = hasUnsavedChanges) {
// Прикажи дијалог потврде
}
Column {
TextField(value = ..., onValueChange = ...)
}
}
Predictive back gesture у Compose-у је подржан почевши од Compose 1.5.0. BackHandler аутоматски обрађује системску анимацију прелаза ако је укључена на уређају. За прилагођену predictive анимацију користите модификатор predictiveBackHandler, који враћа напредак геста од 0 до 1.
Back Press Handling делује једноставно, али у пракси програмери праве низ систематских грешака. Размотримо најчешће проблеме и начине њиховог решавања на основу препорука Google-а и искуства заједнице.
Директан позив finish() у handleOnBackPressed може довести до неочекиваног изласка из апликације ако у стек навигације постоје позадински екрани. Увек проверавајте NavController.backStack кроз Navigation Component или Coordinator пре затварања Activity-ја.
Ако региструјете callback без LifecycleOwner-а (користећи стари addCallback без параметра), callback ће живети вечно и може изазвати NullPointerException ако је Activity већ уништено. Увек користите addCallback(this, callback) са LifecycleOwner-ом.
За модалне прозоре (Bottom Sheet, Dialog) увек постављајте isEnabled = true само када је прозор видљив. Користите addCallback са ламбдом која проверава стање прозора. Navigation Component аутоматски управља овим за NavHost.
Брзи двоструки притисак „Назад” може довести до двоструког позива finish(). Користите флаг или throttleLast за заштиту од поновљених позива у року од 500 ms. Navigation Component уграђено обрађује ову ситуацију, али у прилагођеним сценаријима морате ручно имплементирати заштиту.
private var lastBackPressTime = 0L
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressTime > 500) {
lastBackPressTime = currentTime
navigateBack()
}
}
Често постављана питања
onBackPressed је постао deprecated у Android 11 јер ради само на нивоу Activity-ја. OnBackPressedDispatcher омогућава било којој компоненти (Fragment, Dialog, View) да пресретне притисак кроз јединствени механизам са подршком за животни циклус.
Android систем претвара гест превлачења са ивице у системски притисак „Назад” пре него што га апликација прими. На нивоу OnBackPressedDispatcher-а не можете разликовати ова два догађаја — оба долазе као handleOnBackPressed.
Да, обрада OnBackPressedDispatcher-а је иста за уређаје са три дугмета и са гест навигацијом. Код за пресретање не зависи од типа навигације — систем сам претвара гест у позив диспечера.
Укључите predictive back у Developer Options емулатора Android 13+. Користите ADB: `adb shell settings put global enable_back_animation 1`. Након укључивања, системска анимација ће приказати преглед прелаза при притиску „Назад”.
Проверите два услова: LifecycleOwner мора бити у стању STARTED или RESUMED, а isEnabled callback-а мора бити true. Ако су оба услова испуњена, уверите се да је callback додат у прави OnBackPressedDispatcher — користите requireActivity().onBackPressedDispatcher у Fragment-у.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође