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:
Activity
for the feature that uses the Fragment
with a shared ViewModel
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`
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:
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) }