Fragment

https://developer.android.com/guide/fragments
https://developer.android.com/guide/fragments/lifecycle
https://developer.android.com/guide/fragments/communicate
https://developer.android.com/guide/fragments/fragmentmanager
https://developer.android.com/guide/fragments/transactions
https://developer.android.com/guide/fragments/create
17.10.2022https://habr.com/ru/companies/tbank/articles/693794/
04.10.2022https://habr.com/ru/companies/tbank/articles/691344/
26.09.2022https://habr.com/ru/companies/tbank/articles/690134/
14.09.2022https://habr.com/ru/companies/tbank/articles/688222/
Fragment

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

class MyFragment: Fragment(R.layout.fragment_my) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }
}
onAttach

Вызывается, когда фрагмент прикрепляется к Activity.

override fun onAttach(context: Context) {
    super.onAttach(context)
    println("Fragment attached to Activity")
}
onCreate

Инициализация фрагмента, настройка данных, которые сохраняются при пересоздании.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    println("Fragment created")
}
onCreateView

Создание иерархии View.

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    println("Creating fragment view")
    return inflater.inflate(R.layout.fragment_layout, container, false)
}
onViewCreated

Вызывается сразу после onCreateView. Инициализация компонентов View.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    println("View created")
}
onAcyivityCreated

Вызывается, когда Activity завершила onCreate.

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    println("Activity created for fragment")
}
onViewStateRestored

Восстановление состояния View фрагмента.

override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)
    println("View state restored")
}
onStart

Фрагмент становится видимым для пользователя.

override fun onStart() {
    super.onStart()
    println("Fragment started")
}
onResume

Фрагмент становится активным и начинает взаимодействовать с пользователем.

override fun onResume() {
    super.onResume()
    println("Fragment resumed")
}
onPause

Вызывается перед тем, как фрагмент перестанет быть активным.

override fun onPause() {
    super.onPause()
    println("Fragment paused")
}
onStop

Фрагмент больше не видим.

override fun onStop() {
    super.onStop()
    println("Fragment stopped")
}
onDestroyView

Освобождение ресурсов, связанных с View.

override fun onDestroyView() {
    super.onDestroyView()
    println("Fragment view destroyed")
}
onDestroy

Фрагмент уничтожается. Очищение ресурсов.

override fun onDestroy() {
    super.onDestroy()
    println("Fragment destroyed")
}
onDetach

Фрагмент отсоединяется от Activity.

override fun onDetach() {
    super.onDetach()
    println("Fragment detached from Activity")
}
setRetainInstance

Позволяет сохранить экземпляр фрагмента при пересоздании Activity, например, при повороте экрана. Это удобно для хранения долгоживущих объектов или фоновых задач, которые не должны пересоздаваться.

class MyFragment: Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true
    }
}
FragmentManager

Управляет фрагментами в Android. С его помощью можно добавлять, удалять и заменять фрагменты в активности или другом фрагменте через childFragmentManager. Это позволяет создавать гибкие интерфейсы, которые меняются во время работы приложения.

supportFragmentManager

Поле класса AppCompatActivity, которое предоставляет доступ к экземпляру FragmentManager, совместимому Support Library. Оно используется для управления фрагментами в приложениях, которые используют компоненты из библиотеки поддержки, что позволяет обеспечивать совместимость с более старыми версиями Android.

class MainActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        supportFragmentManager.beginTransaction()
            .add(R.id.fragment_container, FirstFragment())
            .commit()
        }
    }
}
parentFragmentManager

Поле класса Fragment, которое предоставляет доступ к FragmentManager родительского фрагмента. Оно используется, когда есть вложенные фрагменты (child fragments) и необходимо взаимодействовать с родительским FragmentManager, а не с childFragmentManager.

class ChildFragment: Fragment() {

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

        parentFragmentManager
            .beginTransaction()
            .replace(R.id.fragment_container, NewParentFragment())
            .addToBackStack(null)
            .commit()
    }
}
childFragmentManager

Поле класса Fragment, которое предоставляет доступ к FragmentManager для управления дочерними фрагментами. Оно используется, когда есть фрагмент, который содержит другие фрагменты внутри себя (вложенные фрагменты), и нужно управлять их жизненным циклом и взаимодействием.

class ParentFragment: Fragment() {

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

        childFragmentManager
            .beginTransaction()
            .add(R.id.child_fragment_container, ChildFragment())
            .commit()
    }
}
add

Используется для добавления фрагмента в контейнер. При добавлении нового фрагмента через add, предыдущий фрагмент остается активным, его жизненный цикл не меняется, он продолжает работать в RESUMED состоянии.

supportFragmentManager
    .beginTransaction()
    .add(R.id.fragment_container, MyFragment(), "myFragmentTag")
    .commit()
replace

Удаляет текущий фрагмент в контейнере и добавляет новый. Это замещает один фрагмент другим, вызывая полный жизненный цикл нового фрагмента от onCreate() до onResume() и уничтожение предыдущего фрагмента от onPause() до onDestroy().

supportFragmentManager
    .beginTransaction()
    .replace(R.id.fragment_container, FragmentB())
    .commit()
remove

Удаляет фрагмент из FragmentManager, что означает, что фрагмент больше не отображается на экране. Однако состояние фрагмента сохраняется в FragmentManager, и его можно восстановить, если это необходимо.

supportFragmentManager
    .beginTransaction()
    .remove(fragment)
    .commit()
addToBackStack

Добавляет текущую транзакцию фрагментов в стек возврата, что позволяет пользователю вернуться к предыдущему состоянию фрагментов при нажатии кнопки «Назад».

supportFragmentManager
    .beginTransaction()
    .replace(R.id.fragment_container, secondFragment)
    .addToBackStack(null)
    .commit()
popBackStack

Удаляет верхний фрагмент из стека возврата. Это приводит к тому, что фрагмент, который был перед ним, становится видимым, а текущий фрагмент удаляется.

// Удаление последнего фрагмента из стека возврата
supportFragmentManager.popBackStack()

// Удаление всех фрагментов до фрагмента с именем "myFragmentName"
supportFragmentManager.popBackStack("myFragmentName", FragmentManager.POP_BACK_STACK_INCLUSIVE)
popBackStackImmediate

Работает аналогично методу popBackStack, но выполняет операцию немедленно, а не асинхронно.

// Немедленное удаление последнего фрагмента из стека возврата
supportFragmentManager.popBackStackImmediate()

// Немедленное удаление всех фрагментов до фрагмента с именем "myFragmentName"
supportFragmentManager.popBackStackImmediate("myFragmentName", FragmentManager.POP_BACK_STACK_INCLUSIVE)
executePendingTransactions

Заставляет FragmentManager немедленно выполнить все ожидающие транзакции. Это отличается от методов commit или commitNow, которые фиксируют транзакции асинхронно или немедленно, но не обязательно выполняют их сразу.

supportFragmentManager.executePendingTransactions()
commit

Фиксирует изменения в FragmentTransaction, сделанные до вызова этого метода. Это включает в себя добавление, удаление, замену или изменение фрагментов. Выполняет транзакцию асинхронно. Это значит, что изменения могут быть применены позже, в следующем цикле обновления UI. Если вызвать executePendingTransactions после метода commit, то транзакция станет синхронной.

supportFragmentManager
    .beginTransaction()
    .replace(R.id.fragment_container, fragment)
    .commit()
commitNow

Выполняет транзакцию синхронно, немедленно фиксируя изменения. Используйте этот метод, если нужно убедиться, что транзакция завершена до выполнения следующего кода.

supportFragmentManager
    .beginTransaction()
    .replace(R.id.fragment_container, fragment)
    .commitNow()
commitAllowingStateLoss

Фиксирует транзакцию, игнорируя потенциальные потери состояния активити и фрагмента. Это может быть полезно, если состояние не критично и транзакция должна быть выполнена независимо от возможной потери состояния. Использование commitAllowingStateLoss приводит к ситуациям такого вида: Пользователь возвращается на Activity, которая была уничтожена системой. Пользователь ожидает увидеть UI, который отображался перед тем как он покинул активити, но транзакция с добавлением или удалением фрагмента не сохранилась и пользователь видит пустой экран или активити в неверном состоянии. Хорошей практикой считается использование commit, а не commitAllowingStateLoss. Если вы получаете репорты о state loss крэшах, пробуйте решить корень проблемы. Для этого не вызывайте commit в onPause или следующих после него методах.

supportFragmentManager
    .beginTransaction()
    .replace(R.id.fragment_container, fragment)
    .commitAllowingStateLoss()
dismiss

Используется для закрытия DialogFragment. Закрывает диалог с анимацией и планирует удаление фрагмента в следующей доступной транзакции, что позволяет избежать потенциальных проблем с состоянием фрагмента.

class MyDialogFragment: DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(requireContext())
            .setTitle("Title")
            .setMessage("Message")
            .setPositiveButton("OK") { _, _ ->
                // Закрываем диалог с анимацией
                dismiss()
            }
            .setNegativeButton("Cancel") { _, _ -> }
            .create()
    }
}
dismissNow

Используется для немедленного удаления фрагмента. В отличие от dismiss, который может быть отложен до завершения текущего цикла событий, dismissNow выполнит действие сразу же.

class MyDialogFragment: DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(requireContext())
            .setTitle("Title")
            .setMessage("Message")
            .setPositiveButton("OK") { _, _ ->
                // Закрываем диалог немедленно
                dismissNow()
            }
            .setNegativeButton("Cancel") { _, _ -> }
            .create()
    }
}
dismissAllowingStateLoss

Используется для удаления фрагмента, игнорируя потенциальные потери состояния. Он похож на метод dismiss, но позволяет удалять фрагмент даже если это может привести к потере состояния активити или фрагмента.

class MyDialogFragment: DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(requireContext())
            .setTitle("Title")
            .setMessage("Message")
            .setPositiveButton("OK") { _, _ -> }
            .setNegativeButton("Cancel") { _, _ ->
                // Закрываем диалог, позволяя потерю состояния
                dismissAllowingStateLoss()
            }
            .create()
    }
}
POP_BACK_STACK_INCLUSIVE

Удаляет все фрагменты из стека возврата до указанного фрагмента и включает его в удаление. Используется, когда необходимо удалить фрагмент, соответствующий имени или ID, а также все фрагменты, добавленные после него.

// Добавляем фрагменты в стек возврата
supportFragmentManager.beginTransaction()
    .replace(R.id.container, FirstFragment())
    .addToBackStack("firstFragment")
    .commit()

supportFragmentManager.beginTransaction()
    .replace(R.id.container, SecondFragment())
    .addToBackStack("secondFragment")
    .commit()

// Вернуться к FirstFragment и удалить SecondFragment
supportFragmentManager.popBackStack("firstFragment", FragmentManager.POP_BACK_STACK_INCLUSIVE)
POP_BACK_STACK_EXCLUSIVE

Удаляет все фрагменты из стека возврата до указанного фрагмента, но не включает его в удаление. Используется, когда нужно оставить указанный фрагмент на вершине стека.

// Добавляем фрагменты в стек возврата
supportFragmentManager.beginTransaction()
    .replace(R.id.container, FirstFragment())
    .addToBackStack("firstFragment")
    .commit()

supportFragmentManager.beginTransaction()
    .replace(R.id.container, SecondFragment())
    .addToBackStack("secondFragment")
    .commit()

// Вернуться к FirstFragment, оставляя его на вершине стека
supportFragmentManager.popBackStack("firstFragment", FragmentManager.POP_BACK_STACK_EXCLUSIVE)
findFragmentByTag

Ищет фрагмент по заданному тегу. Используется, если фрагмент добавлен в FragmentTransaction с помощью метода add или replace и ему присвоен тег.

val fragment = supportFragmentManager.findFragmentByTag("MY_FRAGMENT")
if (fragment != null) {
    // Фрагмент найден
}
findFragmentById

Ищет фрагмент, привязанный к указанному ресурсу View по его ID. Используется, если фрагмент был добавлен через XML-разметку или динамически с помощью FragmentTransaction и указан ID контейнера.

val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if (fragment != null) {
    // Фрагмент найден
}
FragmentFactory

Управляет созданием фрагментов, упрощая их кастомную инициализацию. Вместо пустого конструктора используется метод instantiate, который можно переопределить для передачи параметров или внедрения зависимостей.

class MainActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val repository = MyRepository()
        supportFragmentManager.fragmentFactory = MyFragmentFactory(repository)
    }
}

class MyFragmentFactory(
		private val repository: MyRepository
): FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        return when (className) {
            MyFragment::class.java.name -> MyFragment(repository)
            else -> super.instantiate(classLoader, className)
        }
    }
}

class MyFragment(
		private val repository: MyRepository
): Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val data = repository.getData()
        return inflater.inflate(R.layout.fragment_my, container, false)
    }
}

• С FragmentFactory удобно использовать DI-фреймворки.

class MyFragmentFactory @Inject constructor(
    private val repository: MyRepository
): FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        return when (className) {
            MyFragment::class.java.name -> MyFragment(repository)
            else -> super.instantiate(classLoader, className)
        }
    }
}
Вопросы на собесе (34)
  • Lifecycle (13)
    1. Жизненный цикл фрагмента?

      onAttach()onCreate()onCreateView()onActivityCreated()onStart()onResume()onPause()onStop()onDestroyView()onDestroy()onDetach()

    1. В каком методе жизненного цикла фрагмента становится доступен Context?

      onAttach()

    1. Вызовется ли метод onDetach() при замене одного фрагмента другим?

      Да. Когда фрагмент удаляется из Activity (включая замену), он проходит через все этапы жизненного цикла, включая onDetach(), что означает, что он отвязывается от Activity.

    1. Зачем введено разделение на onDestroy(), onDestroyView() и onDetach()?

      Разделение на onDestroy(), onDestroyView() и onDetach() позволяет точно управлять жизненным циклом и ресурсами фрагмента:

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

      onDestroy() вызывается при полном уничтожении фрагмента для выполнения завершающих действий.

      onDetach() вызывается при отвязывании фрагмента от активности, что важно для предотвращения утечек памяти.

    1. Почему у фрагментов есть метод onDestroyView(), а у Activity нет?

      Потому что в жизненном цикле фрагментов View может быть уничтожено отдельно от самого фрагмента. Это происходит, когда фрагмент убирается с экрана, но его экземпляр продолжает существовать. У Activity нет такого метода, потому что его View уничтожается вместе с самой Activity при завершении, и нет необходимости разделять уничтожение View и объекта Activity.

    1. Когда у фрагмента вызывается метод onCreate?

      onCreate вызывается, когда фрагмент создается, но еще не связался с пользовательским интерфейсом.

    1. Какие методы жизненного цикла вызовутся у старого фрагмента при replace?

      onPause()onStop()onDestroyView()onDestroy()onDetach()

    1. В каком методе жизненного цикла фрагмента следует подписываться на Flow?

      В onViewCreated(). В этом методе View фрагмента уже создана и доступна для использования, что позволяет безопасно обновлять UI на основе получаемых данных.

    1. Какой lifecycle owner фрагмента следует использовать для подписки на Flow?

      viewLifecycleOwner. Это позволяет автоматически управлять подпиской в соответствии с жизненным циклом представления фрагмента, предотвращая утечки памяти и исключая вызовы на уже уничтоженных вьюхах.

    1. Какие методы жизненого цикла фрагмента существуют?

      oninflate()

      onResume()

      onReload()

      onStart()

    1. Какой метод жизненного цикла фрагмента вызывается, когда фрагмент больше не виден пользователю?

      onDetach()

      onPause()

      onDestroyView()

      onStop()

    1. В каком методе фрагмент становится доступен Activity?

      onCreate()

      onBind()

      onAttach()

      onStart()

    1. Какой метод жизненного цикла фрагмента вызывается для создания иерархии пользовательского интерфейса?

      onCreateView()

      onAttach()

      onCreate()

      onStart()

  • FragmentManager (14)
    1. Для чего нужен FragmentManager?

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

    1. Как работают транзакции у фрагментов?

      Транзакции фрагментов управляют их добавлением, заменой и удалением через FragmentManager. commitNow() выполняет изменения сразу, а commit() — с задержкой; с помощью addToBackStack() можно вернуться к предыдущему состоянию.

    1. Что будет, если мы попытаемся закоммитить фрагмент, когда приложение свернуто?

      Это может вызвать исключение IllegalStateException, так как фрагменты не могут быть изменены, когда Activity не находится в состоянии RESUMED. Для безопасного добавления или замены фрагментов лучше проверять состояние активности и использовать методы, такие как isStateSaved(), чтобы избежать ошибок.

    1. Для чего нужен метод commitAllowingStateLoss?

      Выполняет транзакцию фрагмента, даже если состояние Activity потеряно, что предотвращает исключение при коммите.

    1. Разница методов add и replace?

      Метод add() добавляет новый фрагмент поверх существующего, оставляя предыдущий активным и в состоянии resumed, тогда как метод replace() заменяет текущий фрагмент, вызывая у него полный жизненный цикл (с onPause() до onDetach()) перед добавлением нового фрагмента.

    1. Чем отличается tag в методах add и addToBackStack?

      tag в методе add используется для уникальной идентификации фрагмента в FragmentManager. Это позволяет позже получить доступ к фрагменту с помощью findFragmentByTag.

      addToBackStack создает запись в стек возврата, позволяя пользователю вернуться к предыдущему состоянию интерфейса. Здесь tag не имеет значения, но его можно использовать для более удобной идентификации при работе со стеком.

    1. Как закрыть сразу n фрагментов в стеке?

      Удалить все фрагменты до определенного можно методом fragmentManager.popBackStack("targetFragmentTag", FragmentManager.POP_BACK_STACK_INCLUSIVE).

    1. Есть стек фрагментов A-B-C-D, как из D вернутьс в A?

      Чтобы вернуться с D к A, вызови fragmentManager.popBackStack("A", FragmentManager.POP_BACK_STACK_INCLUSIVE).

    1. Если в контейнер добавлено 5 фрагментов и выполняем replace, сколько фрагментов удалится?

      Удалятся все 5 фрагментов, потому что replace сначала удаляет все существующие фрагменты в указанном контейнере, а затем добавляет новый фрагмент.

    1. Как работает FragmentManager.POP_BACK_STACK_INCLUSIVE?

      Удаляет все фрагменты из стека возврата до указанного фрагмента и включает его в удаление. Используется, когда необходимо удалить фрагмент, соответствующий имени или ID, а также все фрагменты, добавленные после него.

    1. Какие из перечисленных методов для коммита у FragmentTansaction не позволяют добавлять фрагменты в backstack?

      commitNow()

      commit()

      commitAllowingStateLoss()

    1. Какой из следующих классов используется для работы с фрагментами в Android?

      FragmentController

      FragmentManager

      FragmentHandler

      FragmentActivity

    1. Является ли метод FragmentManager.commit() синхронным?

      Да, он синхронный.

      Нет, он асинхронный.

      Такого метода нет.

    1. Какой метод следует использовать для проверки наличия фрагмента в FragmentManager по его тегу?

      locateFragmentByTag

      findFragmentByTag

      searchFragmentByTag

      getFragmentByTag

  • Другие (7)
    1. Для чего нужен Fragment?

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

    1. Является ли Fragment основным компонентом Android?

      Нет.

    1. Чем Fragment отличается от Activity?

      Activity — один из основных Android-компонентов с полным жизненным циклом, представляющий экран, а Fragment — это часть пользовательского интерфейса внутри Activity, зависимая от его жизненного цикла и позволяющая переиспользовать интерфейсные элементы.

    1. Чем Fragment отличается от View?

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

    1. Какие есть способы создать фрагмент?

      Использовать статический метод newInstance() для создания экземпляра и передачи данных через Bundle (является безопасным и правильным подходом).

      Через XML.

      Через FragmentFactory.

    1. Какие есть способы передать данные между фрагментами?

      Arguments

      Shared ViewModel

      Singleton

    1. Какой метод используется для закрытия диалога в Android?

      dismiss()

      cancelDialog

      closeDialog

      dismissDialog