Reputation: 2318
Google states to use SharedViewModel in case of communication between Fragments by scoping it to the activity.
In a Single Activity Application, this means that the activity will be littered with ViewModels which might not be needed anymore and they will stay there for the whole lifecycle. Considering some example like a extended sign up flow or something considering a few screens.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
I came up with the following solution, I want to know how viable or how bad is it and is there any better way around?
Considering I have two Fragments called ExampleOneFragment
and ExampleTwoFragment
for sake of simplicity and I want them to have a shared scope without actually scoping it to the activity. Let's say I want to update text view in ExampleOneFragment
from ExampleTwoFragment
so I create a SharedViewModel like this for both
For ExampleOneFragment
it will be:
private val mSharedViewModel by lazy {
ViewModelProvider(this).get(SharedViewModel::class.java)
}
And For ExampleTwoFragment
I came up with this:
private val mSharedViewModel by lazy {
ViewModelProvider(supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this).get(SharedViewModel::class.java)
}
This seems to be working, but I don't know what kind of issues can this cause.
Some Other Solutions are which I found: According to @mikhehc here We could actually create our very own ViewModelStore instead. This will allow us granular control to what scope the ViewModel have to exist. But I don't understand how to make it work for Fragments?
Secondly, is the hacky way of scoping it to the activity still, but clearing it out via dummy viewmodel by using same key which I found here
Could anyone guide me what is the right approach? I cannot shift to NavGraphs since it is an already up and running project and scoping to activity just feels wrong. Thanks.
Upvotes: 5
Views: 1455
Reputation: 1006714
This seems to be working, but I don't know what kind of issues can this cause.
This code will only work if:
ExampleOneFragment
is created first, alwaysFragmentManager
that ExampleTwoFragment
uses, via a tag of ExampleOneFragment.TAG
, alwaysIf either of those assumptions fail, you will wind up with separate viewmodel instances, because supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this
will resolve to this
.
But I don't understand how to make it work for Fragments?
You would use it the way it is shown in that answer, or by anything else that accepts a ViewModelStoreOwner
. In this line:
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
You would get myApp
from your Fragment
as:
val myApp = requireContext().application as MyApp
Personally, I think the solution that you pointed to is very risky. The ViewModelStore
lives for the entire lifetime of the process and is never cleared. You will wind up sharing your viewmodel across everything, and everything done by that viewmodel is leaked. The concept of creating a custom ViewModelStoreOwner
is fine, but you should be doing something to tie the scope of that owner to the relevant lifetime for the viewmodels that it stores. The answer tries to dance around that in its last paragraph; too many developers will ignore this and run into problems.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
Your app is not written in a way to make automatic ViewModelStoreOwner
management available "out of the box", then.
In the end, you are looking for a ViewModelStoreOwner
to be shared between these two fragments. In your first solution, you are trying to hack that by using the ViewModelStoreOwner
from one of those fragments, which only works if you can reliably choose which fragment that is. In the solution you pointed to in that other answer, you are trying to hack that by intentionally leaking a ViewModelStoreOwner
.
There may be other approaches that you could consider, depending on your circumstances. For example, there may be some option with your dependency inversion framework (Dagger/Hilt, Koin, etc.) to rig up a ViewModelStoreOwner
that is tied to a specific pair of fragment instances.
Upvotes: 3