Компонент навигации. Почему 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() . Эти методы позволяют переключаться между бэк-стеками, сохраняя один бэк-стек и восстанавливая другой.