che10
che10

Reputation: 2318

How to share ViewModel scope between a particular set of Fragments, without using NavGraphs or scoping it to the activity?

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

Answers (1)

CommonsWare
CommonsWare

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:

  • An ExampleOneFragment is created first, always
  • It is added to the same FragmentManager that ExampleTwoFragment uses, via a tag of ExampleOneFragment.TAG, always

If 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

Related Questions