Avatar ·

onBackPressed() объявлен устаревшим в Android 13 (Tiramisu). Как правильно обрабатывать нажатие кнопки «Назад»?

Всем привет! Я только начал изучать Android-разработку на Kotlin и столкнулся с проблемой. В моём приложении есть несколько экранов (фрагментов), и мне нужно переопределить поведение кнопки «Назад». Раньше я использовал такой код: ```kotlin override fun onBackPressed() { if (someCondition) { super.onBackPressed() } else { showExitConfirmationDialog() } } ``` Но теперь Android Studio подчёркивает `onBackPressed()` жёлтым и пишет «Deprecated in Java». Я читал, что это связано с Android 13 (Tiramisu). Пробовал использовать `OnBackPressedDispatcher`, но не могу понять, как правильно его подключить. У меня в проекте стоит библиотека `androidx.activity:activity-ktx:1.7.0`. Мне нужно, чтобы: 1. На главном экране при нажатии «Назад» выходило диалоговое окно с подтверждением выхода. 2. На экране редактирования данных — просто возвращаться назад без подтверждения. 3. Внутри WebView — обрабатывать историю браузера (если есть предыдущая страница, идти назад, иначе — закрывать фрагмент). Подскажите, как правильно мигрировать с устаревшего `onBackPressed()` на новый API? И нужно ли что-то менять для Android 12 и ниже? Заранее спасибо! <<>>

Почему onBackPressed() устарел и что пришло на смену?

Начиная с Android 13 (API 33) метод Activity.onBackPressed() официально помечен как deprecated. Google рекомендует использовать OnBackPressedDispatcher — более гибкий и компонуемый механизм, который позволяет обрабатывать нажатие кнопки «Назад» на разных уровнях (Activity, Fragment, View) без жёсткой привязки к жизненному циклу.

Подготовка: добавьте зависимость

Убедитесь, что у вас в build.gradle (модуль app) есть:

dependencies {
    implementation("androidx.activity:activity-ktx:1.8.2")
    implementation("androidx.fragment:fragment-ktx:1.6.2")
}

Основной подход: OnBackPressedCallback

Создайте callback, который будет управлять поведением. Регистрируйте его через onBackPressedDispatcher.addCallback().

Пример 1: Диалог подтверждения выхода (Activity)

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

        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                AlertDialog.Builder(this@MainActivity)
                    .setTitle("Выход")
                    .setMessage("Вы уверены, что хотите выйти?")
                    .setPositiveButton("Да") { _, _ -> isEnabled = false; onBackPressedDispatcher.onBackPressed() }
                    .setNegativeButton("Нет", null)
                    .show()
            }
        })
    }
}

Ключевой момент: isEnabled = false временно отключает callback, чтобы вызвать стандартное поведение (закрытие Activity).

Пример 2: Разное поведение на разных фрагментах

В фрагменте редактирования (просто возврат назад):

class EditFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Регистрируем callback в onCreate (не в onViewCreated!)
        requireActivity().onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // Просто уходим назад — вызываем стандартное поведение
                isEnabled = false
                requireActivity().onBackPressedDispatcher.onBackPressed()
            }
        })
    }
}

В фрагменте с WebView (обработка истории):

class WebViewFragment : Fragment() {
    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requireActivity().onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (webView.canGoBack()) {
                    webView.goBack()
                } else {
                    isEnabled = false
                    requireActivity().onBackPressedDispatcher.onBackPressed()
                }
            }
        })
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        val view = inflater.inflate(R.layout.fragment_webview, container, false)
        webView = view.findViewById(R.id.webView)
        webView.webViewClient = WebViewClient()
        webView.loadUrl("https://example.com")
        return view
    }
}

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

  • Жизненный цикл: Callback автоматически удаляется, когда вызывается LifecycleOwner.onDestroy() (в нашем случае — фрагмент или Activity).
  • Приоритет: Если несколько callback-ов зарегистрированы, последний добавленный имеет приоритет. Если он не обрабатывает событие (isEnabled = false), управление передаётся следующему.
  • Android 12 и ниже: Код работает на всех версиях, начиная с Android 2.0 (API 1). OnBackPressedDispatcher доступен в androidx.activity:activity-ktx с версии 1.0.0.

Что делать, если нужно глобальное поведение?

Используйте addCallback без указания LifecycleOwner (но это не рекомендуется, так как может привести к утечкам):

onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() { /* ... */ }
})

Полный сценарий для вашего случая

  1. В MainActivity зарегистрируйте callback для диалога выхода.
  2. В каждом фрагменте регистрируйте свой callback, который переопределяет поведение. Не забудьте отключать его, когда нужно передать управление дальше.
  3. Для WebView проверяйте canGoBack() и вызывайте goBack() вместо стандартного закрытия.

Этот подход чище, тестируемее и соответствует современным рекомендациям Google.

<<>>

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

Если вы используете Navigation Component (рекомендуется для приложений с несколькими экранами), можно вообще не писать ручную обработку кнопки «Назад». NavController автоматически обрабатывает навигацию: при нажатии «Назад» он возвращается на предыдущий экран или выходит из приложения, если стек пуст.

// В Activity
val navController = findNavController(R.id.nav_host_fragment)
NavigationUI.setupActionBarWithNavController(this, navController)

// Автоматическая обработка кнопки «Назад»
override fun onSupportNavigateUp(): Boolean {
    return navController.navigateUp()
}

Для кастомного поведения (например, диалог выхода) используйте OnBackPressedCallback вместе с NavController:

onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        if (navController.currentDestination?.id == R.id.mainFragment) {
            // Показываем диалог
            showExitDialog()
        } else {
            isEnabled = false
            onBackPressedDispatcher.onBackPressed()
        }
    }
})

Преимущество: не нужно вручную управлять стеком фрагментов. NavController сам знает, какой экран был предыдущим.

Недостаток: требует миграции на Navigation Component, что может быть избыточно для простых приложений.