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 — колбек, який викликається при натисканні. Якщо enabled = false, натискання передається далі по ланцюжку.
BackHandler автоматично реєструє OnBackPressedCallback в OnBackPressedDispatcher батьківської Activity. Він враховує життєвий цикл composable: при виході з композиції callback видаляється. enabled може бути прив'язаний до стану — наприклад, показувати діалог підтвердження, тільки якщо у формі є незбережені зміни.
@Composable
fun EditScreen(hasUnsavedChanges: Boolean) {
BackHandler(enabled = hasUnsavedChanges) {
// Show confirmation dialog
}
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 мс. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також