Android Hilt scope for fragment ViewModel - android

In the doc, you can see this graph:
So there is this "ActivityRetainedScope" for activity ViewModel, but what about fragment ViewModel? What is the right scope to be used in this case? And the same question for the component, I don't see a component for the fragment ViewModel?

Related

Dagger-Hilt : Why we must annotate Activities which has no injection

Why we must to annotate activities which contains fragments on it ?
Activity has no #Inject but fragments need some dependencies .
I annotate fragments as #AndroidEntryPoint but crash until set this on parent activity.
You can't start a Fragment without an Activity. You have to annotate your Activity with #AndroidEntryPoint because your fragment is a HiltComponent now. When you don't annotate your Activity with #AndroidEntryPoint Hilt would not create a component for this activity and thus could not start the fragment because it didn't create the ActivtyComponent yet.
Hilt activities need to be attached to Hilt applications. Hilt
fragments must be attached to Hilt activities.
See here: https://dagger.dev/hilt/migration-guide (2. Migrate Activities and Fragments)

Android Dagger Hilt: Do we need scope annotations for ViewModels?

in my app I have a MainActivity which requires access to a ViewModel. I am injecting the ViewModel using DaggerHilt and the #ViewModelInject annotation. Additionally, I have two Fragments within the Activity that require access to the same ViewModel in order to pass data to each other using observables.
The problem:
I have found that whenever one of my Fragments go through onDestroy() its ViewModel is killed. This leads me to think that the Activity and Fragments are not sharing the same ViewModel.
My question:
Does anyone know if we are supposed to use scope annotations for ViewModels in Dagger Hilt? I didn't see this stated in the Hilt docs or the android dev tutorials/guides. I had assumed that they were making ViewModels app level singletons, which would make sense.
If we do have to use scope annotations for ViewModels, does anyone know which level is appropriate?
This is my viewmodel code:
class MainActivityViewModel #ViewModelInject constructor(
private val repo: Repo,
private val rxSharedPrefsRepo: RxSharedPrefsRepo,
private val resourcesRepo: ResourcesRepo,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
As per the Scoping in Android and Hilt blog post, using #ViewModelInject means that the objects you pass into the ViewModel are scoped to the ViewModel.
The scope of the ViewModel, however, is based on how you get the ViewModel (what ViewModelStore the ViewModel is associated with) - not anything that Hilt controls. If you use by viewModels() in a Fragment, then the ViewModel is scoped to the Fragment. If you use by activityViewModels() or by navGraphViewModels(), then the ViewModel would be scoped to the activity or navigation graph, respectively.
As mentioned in the blog post, if you want an object that is scoped to the activity and survives configuration changes, you can use Hilt's #ActivityRetainedScoped on any object and inject that object into both fragments.
Whether you should use #ActivityRetainedScoped or a ViewModel where you control the scope separately from Hilt is covered in the blog post:
The advantage of scoping with Hilt is that scoped types are available in the Hilt component hierarchy whereas with ViewModel, you have to manually access the scoped types from the ViewModel.
The advantage of scoping with ViewModel is that you can have ViewModels for any LifecycleOwner objects in your application. For example, if you use the Jetpack Navigation library, you can have a ViewModel attached to your NavGraph.
Hilt provides a limited number of scopes. You might find that you don’t have a scope for your particular use case — for example, when using nested fragments. For that case, you can fall back to scoping using ViewModel.

How to use a shared ViewModel, and avoid reusing the same instance of it every time with Navigation Component

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`
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:
in the nav graph editor, create a nested graph, assigning one id to it
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) }
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.

How can I create common ViewModel for two fragments with a dagger2?

I use Single Activity Architecture in my application and I need to create a ViewModel common for two fragments using Dagger2. If I create ViewModel for Activity Scope, it will live longer than necessary. How can i implement this smartly? Or its ok to use Activity Scope?
From Android developers documentation, the approach to use Activity scope for sharing ViewModel between fragment is fine.
You can refer the document here Share data between fragments

Creating my own ViewModelStore to control ViewModel lifecycle

It is stated in google example that to communicate from fragment to fragment, you could use the ViewModel scoped to the Activity. The problem with this approach is that, then the ViewModel will last until the Activity is destroyed.
In a Single Activity Application, this means that the activity will be littered with ViewModels which might not be needed anymore. You will also have problem with states if these ViewModels won't be cleared properly.
So I look around at how to alter the lifecycle of the ViewModel so that I doesn't have to be tied to the Activity lifecycle but be longer than the lifecycle of a Fragment. This will then be very useful for Multi-step / Transactional flow of screens where the requirements are filled-up during the screen flow process.
So basically, I would want a ViewModel to be scoped less than the activity but longer than a fragment.
To accomplish this, I created my own ViewModelStore and persist it across configuration the same way FragmentActivity persist its own ViewModelStore. Then when initializing the view model I will use,
ViewModelProvider(myCustomViewModelStore, myFactory).get(SomeViewModelClass::class.java)
Since the ViewModel is not scoped to my custom ViewModelStore, I could easily call viewModelStore.clear() to control the lifecycle of the ViewModel.
I was wondering whether this is a good idea and whether someone out there is using the same idea.
Thanks in advance!
As of Navigation Component 2.1.0-aplha02, ViewModels can now be scoped to transaction flows through the Navigation Component navigation graph.

Categories

Resources