英文:
Navigation component - Why ViewModel is not re-created even though its owner (Fragment) is being re-created in NavHostFragment?
问题
我正在提到由Android Studio创建的底部导航视图活动模板项目。
我注意到,每当我点击底部视图以从DashboardFragment
页面切换到另一个片段页面时,然后再次点击底部视图以切换回DashboardFragment
时,将会构建一个新的DashboardFragment
。
我通过在其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")
}
}
然而,令我惊讶的是,DashboardViewModel
,这是一个由DashboardFragment
拥有的ViewModel
,并没有被重新构建。
以下是我在第一次将DashboardFragment
切换为可见时观察到的日志记录。
DashboardFragment constructed
ViewModelProvider(this).get in DashboardFragment onCreateView
DashboardViewModel constructed
当我切换到另一个片段,然后再次切换回DashboardFragment
以进行第二次切换时,我观察到以下日志记录。
DashboardFragment constructed
ViewModelProvider(this).get in DashboardFragment onCreateView
我期望DashboardViewModel
将被重新创建。这是因为ViewModelProvider
将DashboardFragment
作为其所有者,而不是MainActivity
。
val dashboardViewModel =
ViewModelProvider(this).get(DashboardViewModel::class.java)
当第一个DashboardFragment
被销毁时,DashboardViewModel
也应该被销毁。但是,似乎情况并非如此。
我可以知道,在NavHostFragment
中重新创建Fragment
的情况下,为什么ViewModel
没有被重新创建吗?
演示
您可以在https://github.com/yccheok/lifecycle-NavHostFragment/tree/f58b7aa6773de09811e9858a84a3b4614edbe3b3上测试演示。
英文:
I am referring to the Bottom Navigation Views Activity template project created by Android Studio.
I notice, whenever I tap on the bottom view to switch from DashboardFragment
page, to another fragment page. Then, I tap on the bottom view again to switch back to DashboardFragment
, a new DashboardFragment
will be constructed.
I have verified such a behaviour by having logging in its init
function.
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")
}
}
However, to my surprise, DashboardViewModel
, which is a DashboardFragment
owned ViewModel
, is not being re-constructed.
Here's is the logging I am observing, when DashboardFragment
is switched to visible for the 1st time.
> DashboardFragment constructed
>
> ViewModelProvider(this).get in DashboardFragment onCreateView
>
> DashboardViewModel constructed
When I switch to another fragment, and then switch back to DashboardFragment
for the 2nd time, the following is the logging I am observing
> DashboardFragment constructed
>
> ViewModelProvider(this).get in DashboardFragment onCreateView
I am expecting DashboardViewModel
will be re-created again. This is because, ViewModelProvider
is having DashboardFragment
as its owner, and not MainActivity
.
val dashboardViewModel =
ViewModelProvider(this).get(DashboardViewModel::class.java)
When the 1st DashboardFragment
is destroyed, the DashboardViewModel
should be destroyed too. But, seems like this is not the case.
May I know, why ViewModel
is not re-created even though its owner (Fragment) is being re-created in NavHostFragment
?
Demo
You may test the demo at https://github.com/yccheok/lifecycle-NavHostFragment/tree/f58b7aa6773de09811e9858a84a3b4614edbe3b3
答案1
得分: 1
以下是您要翻译的内容:
To expand on Arda Kazancı's answer, the back stack is a record of the navigation history within a NavController
.
Each time you navigate to a Fragment
, that Fragment
is added to the back stack.
When you navigate back, the current Fragment
is popped off the back stack, and the previous Fragment
is recreated.
However, if you are scoping your ViewModel
to the NavController
's lifecycle, the ViewModel
will not be recreated along with the Fragment
; instead, the existing ViewModel
instance will be reused.
When we talk about scoping a ViewModel
to a Fragment
's lifecycle or to a NavController
's lifecycle, we are talking about when the ViewModel
is created and destroyed.
-
If the
ViewModel
is scoped to theFragment
's lifecycle (usingviewLifecycleOwner
), theViewModel
will be created when theFragment
's view is created and destroyed when theFragment
's view is destroyed. This is true whether theFragment
is destroyed because it was popped off the back stack or because of a configuration change like a screen rotation. -
If the
ViewModel
is scoped to theNavController
's lifecycle (the default behavior when usingViewModelProvider(this)
in aFragment
), theViewModel
will be created the first time theFragment
is launched and will not be destroyed until theNavController
is destroyed (which usually happens when the activity hosting theNavController
is finished). This means that theViewModel
will survive even if theFragment
is destroyed and recreated, such as when the user navigates away from theFragment
and then back to it, causing theFragment
to be popped off the back stack and then added back to it.
When you are using the Navigation component along with NavHostFragment
, it uses a slightly different lifecycle scope for the ViewModel
than a standalone Fragment
would. In other words, even though your Fragment
is being recreated, the associated ViewModel
is not.
(See also "Get started with the Navigation component")
This is because NavHostFragment
maintains its own ViewModelStore
that is scoped to the NavController
's lifecycle. This ViewModelStore
is used to store ViewModels
for all destinations (fragments) that are a part of the NavController
.
(See also "Communication of Fragments in the Fragment, using Navigation Component, MVVM and Koin" by Piotr Prus)
When you are calling ViewModelProvider(this).get(DashboardViewModel::class.java)
, the ViewModel
is being retrieved from the ViewModelStore
associated with the NavController
, not the individual fragment. This means that the ViewModel
will persist as long as the NavController
is alive, even if individual fragments are destroyed and recreated.
This design allows data to be persisted across configuration changes and navigation events, which is a common pattern in Android development.
You can observe this behavior by checking the ID of the ViewModel
object:
Log.i("CHEOK", "DashboardViewModel constructed. ID: ${System.identityHashCode(this)}")
You will see that the ID remains the same across different instances of the Fragment
, indicating that it is the same ViewModel
instance.
If you want a ViewModel
that is scoped to the NavController
's lifecycle and survives configuration changes and navigation between Fragments
, you can use navGraphViewModels
:
class DashboardFragment : Fragment() {
private val dashboardViewModel: DashboardViewModel by navGraphViewModels(R.id.nav_graph) { ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!) }
override fun onViewCreated(view, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Use the ViewModel
}
}
Here, R.id.nav_graph
is the ID of your navigation graph. This ViewModel
will be shared between all Fragments
in the navigation graph and will be destroyed when the last Fragment
in the navigation graph is popped off the back stack.
(See also "What is the difference between navGraphViewModels and activityViewModels?")
But then, the original behavior you observed will persist. The DashboardViewModel
will not be re-constructed each time you navigate back to the DashboardFragment
.
The navGraphViewModels
delegate method creates or retrieves a ViewModel
that is scoped to a navigation graph. This means the ViewModel
will be shared between all fragments in the navigation graph and will survive configuration changes and navigation between fragments. The ViewModel
will only be cleared (and thus ready for re-construction) when the last fragment in the navigation graph is popped off the back stack.
If you want the ViewModel
to be scoped to the Fragment
's lifecycle instead of the NavController
's lifecycle, you would have to use the ViewModelProvider
with the Fragment
's viewLifecycleOwner
:
val dashboardViewModel = ViewModelProvider(viewLifecycleOwner).get(DashboardViewModel::class.java)
Note: as noted in "ViewModelProviders is deprecated in 1.1.0", early in 2020, Google had deprecated the ViewModelProviders class, in version 2.2.0 of the AndroidX lifecycle library.
In the context of the AndroidX libraries, the equivalent would be:
dashboardViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)).get(DashboardViewModel::class.java)
With this
referring to the Fragment
itself. ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)
is the Factory
that is used to create the ViewModel
. This factory requires the Application
as a parameter, and it is retrieved from the Fragment
's associated `Activity
英文:
To expand on Arda Kazancı's answer, the back stack is a record of the navigation history within a NavController
.
Each time you navigate to a Fragment
, that Fragment
is added to the back stack.
When you navigate back, the current Fragment
is popped off the back stack, and the previous Fragment
is recreated.
However, if you are scoping your ViewModel
to the NavController
's lifecycle, the ViewModel
will not be recreated along with the Fragment
; instead, the existing ViewModel
instance will be reused.
When we talk about scoping a ViewModel
to a Fragment
's lifecycle or to a NavController
's lifecycle, we are talking about when the ViewModel
is created and destroyed.
-
If the
ViewModel
is scoped to theFragment
's lifecycle (usingviewLifecycleOwner
), theViewModel
will be created when theFragment
's view is created and destroyed when theFragment
's view is destroyed. This is true whether theFragment
is destroyed because it was popped off the back stack or because of a configuration change like a screen rotation. -
If the
ViewModel
is scoped to theNavController
's lifecycle (the default behavior when usingViewModelProvider(this)
in aFragment
), theViewModel
will be created the first time theFragment
is launched and will not be destroyed until theNavController
is destroyed (which usually happens when the activity hosting theNavController
is finished). This means that theViewModel
will survive even if theFragment
is destroyed and recreated, such as when the user navigates away from theFragment
and then back to it, causing theFragment
to be popped off the back stack and then added back to it.
When you are using the Navigation component along with NavHostFragment
, it uses a slightly different lifecycle scope for the ViewModel
than a standalone Fragment
would. In other words, even though your Fragment
is being recreated, the associated ViewModel
is not.
(See also "Get started with the Navigation component")
This is because NavHostFragment
maintains its own ViewModelStore
that is scoped to the NavController
's lifecycle. This ViewModelStore
is used to store ViewModels
for all destinations (fragments) that are a part of the NavController
.
(See also "Communication of Fragments in the Fragment, using Navigation Component, MVVM and Koin" by Piotr Prus)
When you are calling ViewModelProvider(this).get(DashboardViewModel::class.java)
, the ViewModel
is being retrieved from the ViewModelStore
associated with the NavController
, not the individual fragment. This means that the ViewModel
will persist as long as the NavController
is alive, even if individual fragments are destroyed and recreated.
This design allows data to be persisted across configuration changes and navigation events, which is a common pattern in Android development.
You can observe this behavior by checking the ID of the ViewModel
object:
Log.i("CHEOK", "DashboardViewModel constructed. ID: ${System.identityHashCode(this)}")
You will see that the ID remains the same across different instances of the Fragment
, indicating that it is the same ViewModel
instance.
If you want a ViewModel
that is scoped to the NavController
's lifecycle and survives configuration changes and navigation between Fragments
, you can use navGraphViewModels
:
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
}
}
Here, R.id.nav_graph
is the ID of your navigation graph. This ViewModel
will be shared between all Fragments
in the navigation graph and will be destroyed when the last Fragment
in the navigation graph is popped off the back stack.
(See also "What is the difference between navGraphViewModels and activityViewModels?")
But then, the original behavior you observed will persist. The DashboardViewModel
will not be re-constructed each time you navigate back to the DashboardFragment
.
The navGraphViewModels
delegate method creates or retrieves a ViewModel
that is scoped to a navigation graph. This means the ViewModel
will be shared between all fragments in the navigation graph and will survive configuration changes and navigation between fragments. The ViewModel
will only be cleared (and thus ready for re-construction) when the last fragment in the navigation graph is popped off the back stack.
If you want the ViewModel
to be scoped to the Fragment
's lifecycle instead of the NavController
's lifecycle, you would have to use the ViewModelProvider
with the Fragment
's viewLifecycleOwner
:
val dashboardViewModel = ViewModelProvider(viewLifecycleOwner).get(DashboardViewModel::class.java)
Note: as noted in "ViewModelProviders is deprecated in 1.1.0", early in 2020, Google had deprecated the ViewModelProviders class, in version 2.2.0 of the AndroidX lifecycle library.
In the context of the AndroidX libraries, the equivalent would be:
dashboardViewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)).get(DashboardViewModel::class.java)
With this
referring to the Fragment
itself. ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)
is the Factory
that is used to create the ViewModel
. This factory requires the Application
as a parameter, and it is retrieved from the Fragment
's associated Activity
.
Those codes will give you a ViewModel
that is scoped to the Fragment
's lifecycle and will be recreated every time the Fragment
is recreated.
However, keep in mind that this might not be the desired behavior, especially if you want to maintain state across navigation events.
Illustration: "How Android ViewModel works under the hood to survive to configuration change" by Torcheux Frédéric.
答案2
得分: 0
因为“Dashboard Fragment”位于返回堆栈中。它至少已创建一次。它包括了多个返回堆栈的概念。
在某些情况下,您的应用程序可能需要支持多个返回堆栈。一个常见的例子是如果您的应用程序使用底部导航栏。FragmentManager允许您使用**saveBackStack()和restoreBackStack()**方法来支持多个返回堆栈。这些方法让您通过保存一个返回堆栈并恢复另一个来在返回堆栈之间进行切换。
英文:
Because Dashboard Fragment is located in back stack. It has been created at least once. It includes the concept of Multiple BackStacks.
In some cases, your app might need to support multiple back stacks. A common example is if your app uses a bottom navigation bar. FragmentManager lets you support multiple back stacks with the saveBackStack() and restoreBackStack() methods. These methods let you swap between back stacks by saving one back stack and restoring a different one.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论