Reputation: 535
I'm starting to learn about shared view model. Currently I've 3 fragments inside activity, 2 of them are inside nested navGraph.
I want to create shared navGraph viewModel scope to both of them, but I can't understand how and where I can initilaze the view model inside those fragments.
At all my past apps, I created Global view model
private lateinit var viewModel: MainViewModel
And then inside onCreateView
I initlaize the viewModel like this -
viewModel = ViewModelProvider(this, Factory(requireActivity().application)).get(
MainViewModel::class.java)
How can I do the same with navGraph viewModel scope, If I want to share one view model with 2 fragments?
Currently I've this approach:
private val homeViewModel: HomeViewModel by navGraphViewModels(R.id.nested_navigation)
And It's work, but
A. I never saw viewModel intilazied right in the global variable
B. I can't pass variables inside factory with this approach
Upvotes: 7
Views: 3134
Reputation: 81569
private val homeViewModel: HomeViewModel by navGraphViewModels(R.id.nested_navigation)
And It's work, but
A. I never saw viewModel initialized right in the global variable
B. I can't pass variables inside factory with this approach
A.) ViewModel in this case is initialized on first access, so if you just type homeViewModel
in onCreate
or onViewCreated
then it will be created with the right scope.
B.) there's truth to this, you can definitely use a custom factory with navGraphViewModels
, but what you really want (probably) is to implicitly pass any of your Fragment arguments to the ViewModel (note that both of your fragments must have the right keys in their arguments for this to work safely) by using a SavedStateHandle
.
To obtain a SavedStateHandle
, you need to use an AbstractSavedStateViewModelFactory
. To create one, you must create your ViewModel inside onViewCreated
(onCreate
won't work with navgraphs), which is easiest to do by using a ViewModelLazy
.
To create a viewModelLazy
, you can use createViewModelLazy
(what it says on the tin). This can define a way for you to pass in the ViewModelStoreOwner
(which is the NavBackStackEntry
), and the SavedStateRegistryOwner
(which is ALSO the NavBackStackEntry
).
So you can put this in your code and it should work.
inline fun <reified T : ViewModel> SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory(
arguments: Bundle,
crossinline creator: (SavedStateHandle) -> T
): ViewModelProvider.Factory {
return object : AbstractSavedStateViewModelFactory(this, arguments) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(
key: String, modelClass: Class<T>, handle: SavedStateHandle
): T = creator(handle) as T
}
}
inline fun <reified T : ViewModel> Fragment.navGraphSavedStateViewModels(
@IdRes navGraphId: Int,
crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
// Wrapped in lazy to not search the NavController each time we want the backStackEntry
val backStackEntry by lazy { findNavController().getBackStackEntry(navGraphId) }
return createViewModelLazy(T::class, storeProducer = {
backStackEntry.viewModelStore
}, factoryProducer = {
backStackEntry.createAbstractSavedStateViewModelFactory(
arguments = backStackEntry.arguments ?: Bundle(), creator = creator
)
})
}
inline fun <reified T : ViewModel> Fragment.fragmentSavedStateViewModels(
crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
return createViewModelLazy(T::class, storeProducer = {
viewModelStore
}, factoryProducer = {
createAbstractSavedStateViewModelFactory(arguments ?: Bundle(), creator)
})
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ViewModel> Fragment.fragmentViewModels(
crossinline creator: () -> T
): Lazy<T> {
return createViewModelLazy(T::class, storeProducer = {
viewModelStore
}, factoryProducer = {
object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(
modelClass: Class<T>
): T = creator.invoke() as T
}
})
}
Now you can do
private val homeViewModel: HomeViewModel by navGraphSavedStateViewModels(R.id.nested_navigation) { savedStateHandle ->
HomeViewModel(savedStateHandle)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view)
homeViewModel.someData.observe(viewLifecycleOwner) { someData ->
...
}
}
Upvotes: 11