Диалог, который ведёт себя правильно

«А как вы покажете диалог подтверждения?» — казалось бы, простой вопрос. Но на собеседованиях он моментально делит кандидатов на тех, кто пишет «диалоги через AlertDialog и молится», и тех, кто знает про DialogFragment.

Диалоговые окна — это неотъемлемая часть любого Android-приложения: подтверждение действия, выбор из списка, ввод текста. Вопрос про них — любимый у интервьюеров, потому что за ним стоит сразу несколько проверок: понимаете ли вы жизненный цикл, знаете ли разницу между системным Dialog и DialogFragment, умеете ли переживать поворот экрана без потери состояния.

Проблема в том, что «простой» путь — создать AlertDialog и показать его напрямую — в реальном приложении ломается. Диалог не переживает поворот экрана, не сохраняет состояние, не умеет корректно обрабатывать нажатие «Назад». Поэтому в Android есть DialogFragment — и в этой статье разберём, что это такое, как он работает и что говорить на собеседовании.

Что такое DialogFragment

DialogFragment — это фрагмент, специально созданный для показа диалоговых окон. Он наследуется от Fragment и получает все его возможности: собственный жизненный цикл, управление состоянием, восстановление после изменения конфигурации.

Официальная документация говорит прямо: DialogFragment предоставляет все необходимые элементы управления для создания диалогового окна и управления его внешним видом — вместо прямых вызовов методов объекта Dialog.

Зачем это нужно? Три главные причины:

  1. Корректная работа с жизненным циклом. DialogFragment правильно обрабатывает нажатие кнопки «Назад», поворот экрана и уход приложения в фон. Обычный AlertDialog, показанный «в лоб», этим не управляет — при ротации он просто пропадёт или упадёт.
  2. Сохранение состояния. Как и любой фрагмент, DialogFragment восстанавливается после изменения конфигурации, а его состояние можно сохранить через onSaveInstanceState.
  3. Переиспользование UI. Интерфейс диалога можно встроить в экран как обычный фрагмент — например, на планшете показывать его как часть макета, а на телефоне — как диалог поверх экрана.

Главная мысль

DialogFragment — это не «ещё один способ показать диалог», а полноценный фрагмент с жизненным циклом, который умеет быть диалогом. Именно поэтому он управляется через FragmentManager — так же, как обычные экраны-фрагменты.

Кстати, важный нюанс для собеседования: DialogFragment живёт в FragmentManager. Его показывают через транзакцию фрагментов, а не через dialog.show(). Поэтому и back stack, и восстановление после ротации работают так же, как у обычных фрагментов.

Создание: onCreateDialog и show

Создать DialogFragment можно двумя способами: переопределить onCreateDialog() и вернуть готовый диалог (например, AlertDialog) или переопределить onCreateView() и построить собственный layout.

Классический пример из документации Android — диалог через AlertDialog.Builder:

kotlin
StartGameDialogFragment.kt
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment

class StartGameDialogFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            builder.setMessage("Начать игру?")
                .setPositiveButton("Начать") { _, _ ->
                    // Пользователь подтвердил действие
                }
                .setNegativeButton("Отмена") { _, _ ->
                    // Пользователь отменил
                }
            builder.create()
        } ?: throw IllegalStateException("Activity cannot be null")
    }
}

Показывают диалог через show() с указанием FragmentManager и тега:

kotlin
MainActivity.kt
class MainActivity : AppCompatActivity() {

    fun showStartDialog() {
        StartGameDialogFragment().show(supportFragmentManager, "start_game")
    }
}

Тег нужен для того, чтобы найти уже показанный диалог через findFragmentByTag() — например, чтобы не показывать второй такой же.

Для диалога со своим дизайном переопределяют onCreateView() и возвращают инфлейтнутый layout — как в обычном фрагменте. Выбор зависит от задачи:

  • onCreateDialog() — когда достаточно стандартного AlertDialog с сообщением и кнопками;
  • onCreateView() — когда нужен полностью кастомный интерфейс.

Кандидаты говорят «onCreateView и onCreateDialog можно вызвать оба» — и это ошибка. Обычно используют что-то одно. Если переопределён onCreateDialog(), по умолчанию onCreateView() создаёт view, взятое из Dialog; переопределять оба без необходимости — запутывать код.

Жизненный цикл и состояние

Здесь кандидатов ждёт самый коварный вопрос: «Что будет с диалогом при повороте экрана?».

Если вы показали обычный AlertDialog через dialog.show() — при ротации Activity пересоздаётся, а диалог, созданный в onCreate, теряется. Диалог из старого Activity может даже привести к утечке: он держит ссылку на уничтоженную Activity. Это классическая проблема, из-за которой Android-сообщество и перешло на DialogFragment.

DialogFragment при повороте ведёт себя как любой фрагмент: система сохраняет его состояние, пересоздаёт вместе с Activity, и диалог снова появляется на экране. Данные, которые нужно пережить ротацию, сохраняются в onSaveInstanceState — так же, как в обычном фрагменте.

kotlin
ConfirmDialogFragment.kt
class ConfirmDialogFragment : DialogFragment() {

    companion object {
        const val ARG_MESSAGE = "message"

        fun newInstance(message: String): ConfirmDialogFragment {
            return ConfirmDialogFragment().apply {
                arguments = Bundle().apply { putString(ARG_MESSAGE, message) }
            }
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val message = requireArguments().getString(ARG_MESSAGE)
        return AlertDialog.Builder(requireContext())
            .setMessage(message)
            .setPositiveButton("OK") { _, _ -> dismiss() }
            .create()
    }
}

Обратите внимание на два момента в этом коде:

  • arguments вместо передачи параметров через конструктор. Фрагмент может быть пересоздан системой — конструктор с параметрами при этом вызовется снова без аргументов. Данные должны жить в arguments и savedInstanceState. Это обязательное правило для любого фрагмента.
  • requireContext() и requireArguments() — методы, которые бросают исключение, если фрагмент не привязан к контексту или аргументы не заданы. Намного безопаснее, чем молча работать с activity.

Что ответить про ротацию

Обычный AlertDialog создаётся в коде Activity и при пересоздании Activity пропадает. DialogFragment переживает ротацию, потому что он — фрагмент: система сохраняет его состояние и пересоздаёт диалог автоматически. Параметры диалога передаются через arguments, а не через конструктор.

DialogFragment, Dialog и AlertDialog: не путать

На собеседованиях эти три понятия смешивают чаще всего. Разложим по полочкам:

  • Dialog — базовый класс системного окна. Умеет показываться поверх экрана, но сам по себе не управляется жизненным циклом и не сохраняет состояние.
  • AlertDialog — наследник Dialog с готовой структурой: заголовок, контент, до трёх кнопок действий. Это «диалог из коробки» для стандартных сценариев.
  • DialogFragment — фрагмент-контейнер, который показывает внутри себя любой диалог (чаще всего AlertDialog). Добавляет жизненный цикл фрагмента, сохранение состояния и управление через FragmentManager.

Простая аналогия для ответа: AlertDialog — это сам «домик» (что показать), а DialogFragment — «фундамент» (как показать, чтобы пережить поворот экрана, сохранить состояние и корректно закрыться).

Ещё один момент, который любят проверять: как закрыть диалог. У DialogFragment есть метод dismiss(), который убирает диалог и удаляет фрагмент из менеджера. А нажатие системной кнопки «Назад» закрывает диалог и тоже обрабатывается корректно — в отличие от «сырого» Dialog, где за обработку «Назад» вы отвечаете сами.

«А что, если показать диалог после того, как Activity ушла в фон или уничтожена?» — FragmentManager это отсекает: commit() после сохранения состояния Activity не выполнится. Поэтому безопасный паттерн — проверять isAdded или показывать диалог через lifecycleScope/viewLifecycleOwner, а не «просто вызывать show() из любого места».

Ошибки, которые я вижу на собеседованиях

Я провожу технические собеседования и вот какие промахи про диалоги встречаются чаще всего.

  • «Диалог — это AlertDialog.show() и всё». Обычный диалог не переживёт поворот экрана и не сохранит состояние. На вопрос «а если пользователь повернёт телефон?» кандидат теряется.
  • Параметры в конструкторе. ConfirmDialogFragment("text") ломается при пересоздании системой: фрагмент восстановится через пустой конструктор и аргументы не получит. Правильно — newInstance() с arguments.
  • «DialogFragment не нужен, есть Dialog». Это спорная позиция, но в современных приложениях она не выдерживает критики: потеря состояния при ротации — это баг, который пользователь увидит сразу.
  • Путают onCreateDialog и onCreateView. Обычно выбирают один способ: стандартный AlertDialog — через onCreateDialog, кастомный layout — через onCreateView.
  • Забывают, что DialogFragment — это фрагмент. Значит, работают все правила фрагментов: arguments, FragmentManager, back stack, onSaveInstanceState. Кто это проговаривает — сразу выделяется на фоне остальных.

Если вы собираетесь на собеседование в компанию уровня Яндекс или Авито, вопрос про диалоги почти гарантированно всплывёт — обычно в связке с жизненным циклом и сохранением состояния. Это базовая тема, но именно на ней видно, писал ли человек реальные приложения или только читал про компоненты.

Хотите проверить готовность к собеседованию?

Бесплатная диагностика: разберу ваши пробелы и составлю план роста за 30–40 минут. Без обязательств — если менторство вам не подойдёт, честно скажу.

Итоги

Три тезиса, которые нужно держать в голове перед ответом про DialogFragment.

DialogFragment — это фрагмент, который умеет быть диалогом. Он наследует все возможности Fragment: жизненный цикл, управление через FragmentManager, сохранение состояния. Поэтому он корректно переживает поворот экрана и нажатие «Назад» — то, с чем «сырой» AlertDialog не справляется.

Создание — через onCreateDialog() или onCreateView(), показ — через show(manager, tag). Стандартный диалог с кнопками — AlertDialog.Builder в onCreateDialog(). Кастомный интерфейс — layout в onCreateView(). Параметры передаются через arguments, а не через конструктор.

Dialog и AlertDialog — это «что показать», DialogFragment — «как показать правильно». Если интервьюер копает в сторону состояния при ротации — отвечайте через механику фрагментов, а не через методы диалога.

Формула ответа

DialogFragment = Fragment + Dialog. Как фрагмент — живёт в FragmentManager, переживает ротацию, сохраняет состояние через arguments и onSaveInstanceState. Как диалог — показывает AlertDialog или кастомный view и закрывается через dismiss(). Проговорили эту связку — вопрос закрыт.

Если хотите потренироваться отвечать на такие вопросы перед собеседованием — напишите в Telegram, разберём вашу ситуацию и составим план подготовки.