Retaining ViewModel in fragments - android

I'm collaborating with ViewModel and fragments, and would like to retain my ViewModel for my fragment on rotation change. When passing my Fragment into ViewModelProviders.of() it does not get retained, but when I pass the Activity that the fragment belongs to, it is retained. So is passing the activity how it is supposed to be used?
Calling ViewModelProviders.of(this) in Fragment won't retain my ViewModel. Is that expected behavior?
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
fun OnXXXXXXXXX {
// This _will NOT_ retain ViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
// This _will_ retain ViewModel
viewModel = ViewModelProviders.of(activity).get(MainViewModel::class.java)
}
}

Yes, its expected behaviour, Look at this content
Fragments can share a ViewModel using their activity scope to handle this communication
If you want to share same ViewModel, use same context. For Example, multiple fragments on same activity:
ViewModelProviders.of(activity)

Related

Use viewmodel for a fragment called twice

I have a navigation as follows:
FragmentList -> FragmentDetailA -> FragmentDetailB -> FragmentDetailC
I use a viewModel for the detail FragmentDetailViewModel
private val detailViewModel: DetailViewModel by activityViewModels()
But if I go forward, and then back, the above fragmentDetails are changed. How to have a viewmodel assigned to the fragment without changing the others?
Solution:
First of all, change activityViewModels() for viewModels()
Then, the problem was with setFragmentResultListener. It was called before instantiating the new fragment, then the call was made on Fragment A and not on Fragment B. I decided to pass data between destinations with Bundle objects.
Thanks a lot
With this way that you initialized view model, view model depends on the parent activity you must use this way
private val detailViewModel: DetailViewModel by viewModels()
to your view model instance depends on your fragment instance.
It sounds like you want to link the viewmodel to the fragment.
ViewModelProvider(this,viewModelFactory).get(MyViewModel.class)

How to share the same ViewModel with ViewPager, Navigation and Hilt?

I'm trying to use the same ViewHolder between this fragments.
FragmentA, FragmentB and Fragment E are in a navGraph, Fragment C and D are not.
I tried using by viewModels() but it initialized the ViewModel 3 times (on A, C, E)
Using by activityViewModels() it initilized the ViewModel 2 times (on A and C)
I also tried by viewModels(ownerProducer = {requireParentFragment().requireParentFragment()}) in Fragment C and D but it keeps initializing the ViewModel.
And the last thing i tried is inserting Fragment A, B and E into a navGraph and using
by navGraphViewModels(R.id.customNavGraph) { defaultViewModelProviderFactory } but got the same result as above.
In conclusion, Fragment C and D keep initializing the ViewModel
How can i fix this?
Have you tried accessing the viewModels extension function on requireParentFragment(). And to ensure that the viewModel is called after the child fragment is attached to the parent/host, you can initialize it lazily or just initialize it after the fragment is attached to the parent
private val viewModel by lazy { requireParentFragment().viewModels<YourViewModel>() }

How to share ViewModel between Fragments with application context?

How do I get my Fragments to share the same instance of a ViewModel?
In my ViewModel I need application context so I use "AndroidViewModel" but my MainFragment and DialogFragment don't share the same instace of ViewModel.
How can I achive this?
Here is how I initialized my ViewModels in my MainFragment and DialogFragment:
mViewModel = ViewModelProvider.AndroidViewModelFactory.
getInstance(getActivity().getApplication())
.create(MyViewModel.class);

How to have separate instance of ViewModel in each Fragment and have shared data as well between Viewmodels

I am working on Android TV app
And I have a Fragment(main Fragment) and inside fragment have side menu, each menu Item creates new fragment(menu Fragment).
I have Viewmodel and in ViewModel I have config livedata which I am loading when I main Fragment is created.
And Menu Fragment's data is based on data I am getting from API calls and from config data
I have created single instance of viewmodel with the activity lifecycle.
But the problem is when I am navigating from one fragment to another for example from 1-2 fragment and as 1 fragment has already loaded data the livedata is not empty and navigating to 2nd fragment before fetching the second fragment data it observes/displays the livedata from first fragment and then it's own after it fetches it's data
I think each fragment should have it's own instance ,but I also need data which should be shared (the config) between the each instance of viewmodel ?
How can I make this?
private val viewModel: HomeViewModel by lazyViewModelActivityScope()
viewModel.fetch()
viewModel.configData.observe(viewLifecycleOwner, Observer { it ->
loadData(it)
})
it is inside MenuFragment
private val viewModel: HomeViewModel by lazyViewModelActivityScope()
viewModel.fetchMenuPage(menuItem)
viewModel.carouselsWithAssetsData.observe(viewLifecycleOwner, Observer { carouselWithAssets ->
carouselWithAssets.forEach { carouselWithAsset ->
mRowsAdapter.add(createCardRow(carouselWithAsset))
}
})
I got the answer of my question and I want to share it with others
I think generally this is something you'd manage with DI (injecting the same config into both ViewModels)
Or each Fragment would need to pass the activity ViewModel's data to the Fragment's ViewModel. Or you could have a global LiveData that manages itself (loading data in onActive()) and skip the activity ViewModel entirely

Support Fragment Manager .replace() and ViewModel

It appears that whenever you use the .replace() method within a transaction with the Support Fragment Manager the ViewModel is recreated. Is this intentional? The Fragment instance itself is not changing and the ViewModel will be (partially) preserved during rotation/configuration changes.
I'm seeing the following scenarios:
Get View model ref (count = 0), update count = 1, rotate, count = 1, onCreate called again and count = 0 (view model recreated).
Call .replace() and view model is recreated (activity and fragment instances unchanged).
Using support library 26.0.0.
The ViewModel is being created in onCreate of my fragment and is scoped to the Fragment:
viewModel = ViewModelProviders.of(this).get(DashboardViewModel::class.java)
Anyone shed some light on if this is normal?
As #CommonsWare mentioned, the viewmodel instance inside fragment should be the same in Activity.
Therefore, inside the Activity , you should do something like this
MyViewModel vm = ViewModelProviders.of(this).get(MyViewModel.class);
Inside the Fragment , you should do something like this
MyViewModel vm = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
In result, they will use the same instance.
However, if you try to use it inside the fragment
MyViewModel vm = ViewModelProviders.of(this).get(MyViewModel.class);
The Viewmodel will be recreated in the fragment when you rotate the device. Since the instance is preserved inside the fragment not the activity, when the activity recreated, the fragment will also be recreated, and the MyViewModel instance.
Try to take a look on the example on master detail fragment (which might be easy to resolve your problem) ViewModel in Android Developer
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
I have also made the simple Master Detail template on the github
SimpleDetailActivity.java
SimpleDetailFragment.java
replace() is suppossed to be called when you make different fragment. For the same fragment you call update().
replace() method means you can replace your current Fragment with something different fragment, which has something different layout (physical structure). You can't even guarantee that it starts from the same memory that the previous fragment used. ViewModel is kind of layout for whole container. So, for the object with different physical structure and (possibly with different memory -- I am writing possibly because you can replace with the same fragment also), you have to recreate different ViewModel to define it's container. This is because one ViewModel object points one reference Container, next time you have different fragment, your fragment container is defined by ViewModel is somewhere else, so you need another ViewModel object to point that Fragment Container.
But when you do update(), or rotate(), you guarantee the updated fragment's memory space can decrease/increase but still it's starting memory remains same. So no need to create the ViewModel. This is because your old ViewModel object is referencing the same old Fragment's container.
When you do create(), it creates GUI everything, so, obviously, there happen ViewModel creation again.
The tracking of ViewModel count is based on the above explanation.
The ViewModel is being created in onCreate of my fragment and is scoped to the Fragment. This is kind of power delegation to the fragment.

Categories

Resources