Avatar ·

onBackPressed() устарел в Android 13. Как правильно обрабатывать нажатие кнопки "Назад"?

Всем привет. Я разрабатываю приложение на Kotlin под Android и столкнулся с проблемой. В моей Activity я переопределял метод `onBackPressed()`, чтобы, например, показать диалог подтверждения выхода или выполнить какие-то действия перед закрытием экрана. Но при обновлении targetSdk до 33 (Android 13) компилятор выдаёт предупреждение, что `onBackPressed()` устарел (deprecated). Вот как выглядел мой старый код: ```kotlin class MainActivity : AppCompatActivity() { override fun onBackPressed() { // Показываем диалог подтверждения AlertDialog.Builder(this) .setTitle("Выход") .setMessage("Вы уверены, что хотите выйти?") .setPositiveButton("Да") { _, _ -> super.onBackPressed() } .setNegativeButton("Нет", null) .show() } } ``` Теперь студия подчёркивает `onBackPressed()` жёлтой волнистой линией и рекомендует использовать `OnBackPressedDispatcher`. Я попробовал почитать документацию, но она показалась мне слишком запутанной для новичка. Подскажите, пожалуйста, как правильно мигрировать на новый API? Что такое `OnBackPressedDispatcher` и `OnBackPressedCallback`? Как теперь обрабатывать нажатие кнопки "Назад" в Activity и во Fragment? Заранее спасибо за помощь! <<>>

Почему onBackPressed() устарел?

Google отказался от прямого переопределения onBackPressed() в Activity, потому что этот метод не учитывает современную архитектуру Android, где навигация часто управляется фрагментами и Jetpack Navigation. Новый API — OnBackPressedDispatcher — позволяет добавлять обработчики нажатия "Назад" на любом уровне: в Activity, Fragment, View или даже в отдельном компоненте, и они будут работать согласованно.

Основные компоненты нового API

  • OnBackPressedDispatcher — диспетчер, который управляет всеми обработчиками нажатия "Назад". Он есть у каждой Activity и Fragment.
  • OnBackPressedCallback — абстрактный класс, который вы расширяете, чтобы определить своё поведение при нажатии "Назад".

Как использовать в Activity

Вместо переопределения onBackPressed() в Activity, нужно зарегистрировать callback через onBackPressedDispatcher. Пример с диалогом подтверждения:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Регистрируем callback
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // Показываем диалог
                AlertDialog.Builder(this@MainActivity)
                    .setTitle("Выход")
                    .setMessage("Вы уверены, что хотите выйти?")
                    .setPositiveButton("Да") { _, _ -> 
                        // Если пользователь подтвердил — вызываем isEnabled = false и повторно диспатчим
                        isEnabled = false
                        onBackPressedDispatcher.onBackPressed()
                    }
                    .setNegativeButton("Нет", null)
                    .show()
            }
        })
    }
}

Важные моменты:

  • Параметр true в конструкторе OnBackPressedCallback означает, что callback активен. Если поставить false, он будет игнорироваться.
  • Внутри handleOnBackPressed() вы можете выполнить свою логику. Если нужно завершить Activity стандартным образом (как при обычном нажатии "Назад"), вы должны временно отключить свой callback (isEnabled = false) и вызвать onBackPressedDispatcher.onBackPressed() снова — тогда сработает следующий обработчик в цепочке (обычно системный, который закрывает Activity).
  • Метод addCallback принимает LifecycleOwner (this — Activity) — это автоматически удалит callback, когда Activity будет уничтожена.

Как использовать во Fragment

Во Fragment регистрация callback происходит аналогично, но через requireActivity().onBackPressedDispatcher:

class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // Ваша логика, например, показать Snackbar
                Snackbar.make(view, "Нажмите ещё раз для выхода", Snackbar.LENGTH_SHORT).show()
            }
        })
    }
}

Обратите внимание: используется viewLifecycleOwner, а не this (lifecycle фрагмента). Это гарантирует, что callback будет активен только пока существует View фрагмента, и автоматически удалится при его уничтожении.

Работа с Jetpack Navigation

Если вы используете Jetpack Navigation, то в большинстве случаев вообще не нужно писать свой callback — библиотека сама обрабатывает нажатие "Назад" для переходов между экранами. Но если нужно перехватить нажатие на конкретном экране (например, чтобы показать диалог "Сохранить изменения?"), делается это так же, как во Fragment выше.

Итог

Новый API — это гибкая система, которая позволяет добавлять обработчики нажатия "Назад" на любом уровне иерархии. Главные правила:

  • Не переопределяйте onBackPressed() в Activity.
  • Используйте onBackPressedDispatcher.addCallback() с подходящим LifecycleOwner.
  • Если нужно выполнить действие и потом передать управление дальше — временно отключите свой callback.
<<>>

Альтернативный подход: использование OnBackPressedCallback в ViewModel

Если вы придерживаетесь MVVM и хотите, чтобы логика обработки нажатия "Назад" была в ViewModel (например, для сохранения данных или проверки изменений), можно передать callback во ViewModel через фабрику или напрямую.

class MyViewModel(private val onBackPressedCallback: OnBackPressedCallback) : ViewModel() {
    fun onSaveAndExit() {
        // Сохраняем данные
        onBackPressedCallback.isEnabled = false
        onBackPressedCallback.handleOnBackPressed() // или вызвать диспетчер
    }
}

Но будьте осторожны: ViewModel переживает поворот экрана, а callback привязан к жизненному циклу Activity/Fragment. Лучше хранить callback в Activity/Fragment и вызывать методы ViewModel из handleOnBackPressed().

Простой способ для быстрого перехода

Если вам нужно просто заменить старое поведение без сложной логики, можно оставить вызов super.onBackPressed() внутри нового callback, но это не рекомендуется, так как теряется гибкость. Лучше сразу привыкать к новому API.

Также можно использовать библиотеку androidx.activity:activity-ktx, которая предоставляет расширения для работы с OnBackPressedDispatcher — она уже включена в стандартные зависимости Android-проектов.