3

My app consists of one single Activity with multiple Fragments, following the "single Activity app model", so that I can implement properly navigation using the Navigation Component in Android jetpack.

Most of my screens (Fragments) are standalone and don't depend on each other, hence they use their own ViewModel

Some features require navigation involving more than one Fragment. As those features share data between them that are passed back and forth through the Fragments, I use a shared ViewModel (as recommended by Google). I need to use the same instance of the shared ViewModel in all the associated Fragments, as I need the Fragments share the state of the shared ViewModel.

To use the same instance of the ViewModel in these associated Fragments, I need to create the ViewModel using the parent Activity (not the Fragment) when getting the ViewModel from the ViewModelProviders:

val viewModel = ViewModelProviders.of(
            parentActivity, factory.create()
        ).get(SharedViewModel::class.java)

This works, however, It produces one problem: when navigating a consecutive times to the first Fragment that requires the shared ViewModel, ViewModelProviders.of() will return the same instance of the ViewModel as before: The ViewModel is being shared between the Fragments, but also between different navigations to the feature implemented like this.

I understand why this is happening (Android is storing the ViewModel in a map, which is being used when requesting the ViewModel with ViewModelProviders.of()), but I don't know how I am expected to implement the "shared ViewModel pattern" properly.

The only workarounds I see are:

  • Create a different Activity for the feature that uses the Fragment with a shared ViewModel
  • Use nested Fragments, and use common parent Fragment for the feature that uses the Fragment with a shared ViewModel

With these two options, I would be able to create a ViewModel that will be shared between the Fragments intervening in the feature, and will be different each time I navigate to the feature.

The problem I see here is that this seems to be that against the fundamentals of the Navigation Component and the single Activity app. Each feature implemented this way will need to have a different navigation graph, as they will use a different navigation host. This would prevent me from using some of the nice features of Navigation Component.

What is the proper way to implement what I want? Am I missing anything, or is it the way it is?

Before Navigation Component I would use different Activities and Fragments and use Dagger scopes associated with the Activity/Fragment to achieve this. But I'm not sure what's the best way of implementing this with just one Activity`

GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61

2 Answers2

3

I have discovered this can be done starting with 2.1.0-alpha02

From: https://developer.android.com/jetpack/androidx/releases/navigation#2.1.0-alpha02

You can now create ViewModels that are scoped at a navigation graph level via the by navGraphViewModels() property delegate for Kotlin users or by using the getViewModelStore() API added to NavController. b/111614463

Basically:

  1. in the nav graph editor, create a nested graph, assigning one id to it
  2. when providing the ViewModel, do not do it from the Activity. Instead, use the navGraphViewModels extension function of Fragment.

Example:

Nested graph in the nav graph

<navigation
        android:id="@+id/feature_nested_graph"
        android:label="Feature"
        app:startDestination="@id/firstFragment">
        <argument
            android:name="item_id"
            app:argType="integer" />
        <fragment
            android:id="@+id/firstFragment"
            [....]
        </fragment>
        [....]
    </navigation>

For getting the ViewModel scoped to feature_nested_graph nested nav gaph:

        val viewModel: SharedViewModel
                by fragment.navGraphViewModels(R.id.feature_nested_graph)

or, if are injecting into the ViewModel and you are using a custom factory for that:

        val viewModel: SharedViewModel
                by fragment.navGraphViewModels(R.id.feature_nested_graph) { factory2.create(assessmentId) }
GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61
0

You have the same instance of a shared ViewModel, because it belongs to the Activity - that's clear. I don't know exactly your use case, but usually when I need to do similar, I simply notify the ViewModel from Fragment's onCreate or orCreateView passing some identifier. In your case it could be something like:

viewModel.onNavigatedTo("fragment1")

This way the shared view model can differentiate what fragment currently uses it and refresh the state accordingly.

Derek K
  • 2,756
  • 1
  • 21
  • 37