Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initilaize viewModel using navGraph scope

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

like image 423
Noam Avatar asked May 06 '20 08:05

Noam


People also ask

How to create Nav graph scoped viewmodels in Java?

You can create nav graph scoped viewmodels in Java by first getting the viewmodel store. A viewmodel store is basically just a container that stores viewmodels. Then get the viewModel by passing the viewModelStore into a viewmodelProvider. See, I told you it was super simple.

How do I create a view model at the navigation graph level?

Scoping ViewModels to a navigation graph: You can now create ViewModels that are scoped at the navigation graph level using the by navGraphViewModels () property delegate for Kotlin users using the -ktx libraries or by using the getViewModelStoreOwner () API added to NavController.

Is it possible to use a custom factory with navgraphviewmodels?

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.

What is navgraphviewmodels in Kotlin?

3a For Kotlin users, there is a sweet new extension function called navGraphViewModels. navGraphViewModels is a kotlin extension function for fragments that creates nav graph scoped view models. It accepts two parameters: a nav graph ID and also a viewmodel factory. Val order selection by navGraphViewModels (R.id.order_selection_nav_graph).


1 Answers

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 ->
        ...
    }
}
like image 151
EpicPandaForce Avatar answered Sep 20 '22 14:09

EpicPandaForce