Компонент навигации. Почему ViewModel не создается заново, хотя его владелец (фрагмент) воссоздается в NavHostFragment?

Я имею в виду проект шаблона действий Bottom Navigation Views , созданный Android Studio.

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

Я проверил такое поведение, войдя в свою учетную запись.initфункция.

DashboardFragment.kt

      class DashboardFragment : Fragment() {

    ...

    init {
        Log.i("CHEOK", "DashboardFragment constructed")
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        Log.i("CHEOK", "ViewModelProvider(this).get in DashboardFragment onCreateView")

        val dashboardViewModel =
            ViewModelProvider(this).get(DashboardViewModel::class.java)

DashboardViewModel.kt

      class DashboardViewModel : ViewModel() {

    ...

    init {
        Log.i("CHEOK", "DashboardViewModel constructed")
    }
}

Однако, к моему удивлению, принадлежащий ему дом не реконструируется.


Вот журнал, который я наблюдаю, когда он впервые переключается на видимый .

Создан фрагмент панели мониторинга.

ViewModelProvider(this).get в DashboardFragment onCreateView

Создана модель DashboardViewModel.

Когда я переключаюсь на другой фрагмент, а затем переключаюсь обратно во второй раз , я наблюдаю следующую запись:

Создан фрагмент панели мониторинга.

ViewModelProvider(this).get в DashboardFragment onCreateView

Я ожидаю, что он будет воссоздан снова. Это потому что,ViewModelProviderимеет в качестве владельца, а неMainActivity.

      val dashboardViewModel =
            ViewModelProvider(this).get(DashboardViewModel::class.java)

Когда 1-еDashboardFragmentразрушен,DashboardViewModelтоже должен быть уничтожен. Но, похоже, это не так.

Могу я узнать почемуViewModelне создается заново, даже если его владелец (фрагмент) воссоздается вNavHostFragment?

Демо

Вы можете протестировать демо-версию по адресу https://github.com/yccheok/lifecycle-NavHostFragment/tree/f58b7aa6773de09811e9858a84a3b4614edbe3b3.

2 ответа

Если раскрыть ответ , то представляет собой запись истории навигации в файле Арды Казанджи.Каждый раз, когда вы переходите к задний стек
, который добавляется в задний стек.
При переходе назад текущий элемент извлекается из заднего стека, а предыдущий создается заново.

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

Когда мы говорим об ограничении жизненного цикла a или жизненного цикла a, мы говорим о том, когда создается и уничтожается.

  • Если область действия ограничена жизненным циклом (с использованием ), он будет создан при создании представления и уничтожен при уничтожении представления. Это верно независимо от того, уничтожен ли объект из-за его извлечения из заднего стека или из-за изменения конфигурации, например поворота экрана.

  • Если область действия ограничена жизненным циклом (поведение по умолчанию при использованииViewModelProvider(this)в a ), будет создан при первом запуске и не будет уничтожен до тех пор, пока не будет уничтожен (что обычно происходит после завершения активности, на которой размещено ). Это означает, что выживет, даже если будет уничтожен и воссоздан, например, когда пользователь уходит от и затем возвращается к нему, в результате чего объект извлекается из заднего стека, а затем добавляется обратно в него.

Когда вы используете компонент навигации вместе с , он использует немного другую область жизненного цикла, чем автономный. Другими словами, даже если ваш объект воссоздается, связанный — нет.

(См. также « Начало работы с компонентом навигации »).

Это потому чтоNavHostFragmentподдерживает свой собственный это ограничено жизненным циклом . Это используется для храненияViewModelsдля всех направлений (фрагментов), входящих в состав .

(См. также « Связь фрагментов во фрагменте с использованием навигационного компонента, MVVM и Koin » Петра Пруса )

Когда ты звонишьViewModelProvider(this).get(DashboardViewModel::class.java), извлекается изViewModelStoreсвязанный с , а не с отдельным фрагментом. Это означает, что объект будет существовать до тех пор, пока он жив, даже если отдельные фрагменты будут уничтожены и воссозданы.

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

Вы можете наблюдать такое поведение, проверив идентификатор объекта:

      Log.i("CHEOK", "DashboardViewModel constructed. ID: ${System.identityHashCode(this)}")

Вы увидите, что идентификатор остается одинаковым для разных экземпляров , что указывает на то, что это один и тот же экземпляр.


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

      class DashboardFragment : Fragment() {

    private val dashboardViewModel: DashboardViewModel by navGraphViewModels(R.id.nav_graph) { ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!) }

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

        // Use the ViewModel
    }
}

Здесь,R.id.nav_graph— это идентификатор вашего навигационного графа. Это будет разделено между всемиFragmentsв навигационном графе и будет уничтожен, когда последний элемент навигационного графа будет извлечен из заднего стека.

(См. также « В чем разница между navGraphViewModels и ActivityViewModels? »)

Но тогда исходное поведение, которое вы наблюдали, сохранится. DashboardViewModelне будет перестраиваться каждый раз, когда вы возвращаетесь кDashboardFragment.

The navGraphViewModelsМетод делегата создает или извлекает объект, область действия которого ограничена графом навигации. Это означает, что он будет использоваться всеми фрагментами в навигационном графе и сохранится при изменении конфигурации и навигации между фрагментами. Будет очищен (и, следовательно, готов к повторной конструкции) только тогда, когда последний фрагмент навигационного графа будет извлечен из заднего стека.


Если вы хотите, чтобы жизненный цикл был ограничен жизненным циклом , а неNavControllerжизненного цикла, вам придется использоватьViewModelProviderс буквой "s"viewLifecycleOwner:

      val dashboardViewModel = ViewModelProvider(viewLifecycleOwner).get(DashboardViewModel::class.java)

Примечание. Как отмечено в разделе « ViewModelProviders устарел в версии 1.1.0 », в начале 2020 года Google объявила устаревшим класс ViewModelProviders в версии 2.2.0 библиотеки жизненного цикла AndroidX .

В контексте библиотек AndroidX эквивалентом будет:

      dashboardViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)).get(DashboardViewModel::class.java)

Сthisобращаясь к самому себе.ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)этоFactoryкоторый используется для создания . Этому заводу требуетсяApplicationв качестве параметра, и он извлекается из связанногоActivity.

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

Иллюстрация: « Как Android ViewModel работает под капотом, чтобы выжить при изменении конфигурации », автор Torcheux Frédéric.

Потому что фрагмент панели мониторинга находится в заднем стеке. Он был создан как минимум один раз. Он включает в себя концепцию Multiple BackStacks.

В некоторых случаях вашему приложению может потребоваться поддержка нескольких обратных стеков. Типичный пример: ваше приложение использует нижнюю панель навигации. FragmentManager позволяет поддерживать несколько обратных стеков с помощью методов saveBackStack() и restartBackStack() . Эти методы позволяют переключаться между бэк-стеками, сохраняя один бэк-стек и восстанавливая другой.

Другие вопросы по тегам