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

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>() }

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)

One ViewModel Instance for Multiple Fragment

I have 1 activity which contains parent fragment. Inside the parent fragment is 2 child fragments. I want to only create one viewmodel instance and use it in both child fragment.
This is my code in parent fragment:
val factory = ViewModelFactory.getInstance(requireContext())
viewModel = ViewModelProvider(this, factory)[FavoriteViewModel::class.java]
And this one in child fragment:
viewModel = (requireParentFragment() as FavoriteParentFragment).viewModel // force close
It force close without error in logcat.
When i try to move the parent fragment's code to the activity (without changing the code) and change the child fragment's code to:
viewModel = (activity as MainActivity).viewModel // works
It works.
So is it possible to do the first method (that is force closed)? If so, how to do that properly and why i did not get any logcat error.
Instead of trying to access the viewmodel from the parent directly, use the scope of the parent to get a shared viewmodel.
Inside your child fragments, use this code
val factory = ViewModelFactory.getInstance(requireContext())
val viewModel = ViewModelProvider(requireParentFragment(), factory).get(FavoriteViewModel::class.java)
Using the above code, you no longer need to store an instance of the viewmodel in the parent fragment.

how to pass a value back to previous fragment destination using Android navigation component?

let sat I have some destinations like this
from fragment A --> to fragment B --> to fragment C
I can use Safe Args to pass data from fragment A to fragment B. and also using safe args from fragment B to fragment C.
what if I want to bring a string that generated in fragment C back to fragment B or to fragment A ?
to navigate from fragment C to fragment B, I use code:
Navigation.findNavController(fragmentView).navigateUp()
and to navigate from fragment C to fragment A, I use code:
Navigation.findNavController(view).popBackStack(R.id.fragmentA, false)
so how to pass a value back to previous fragment destination ? do I have to make an action back from fragment C to fragment B and pass the value through safe args ?
really need your help. Thank you very much :)
You can use the below snippet for sending data to previous fragment.
Fragment A observing the data:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(viewLifecycleOwner) { data ->
// Do something with the data.
}
}
Fragment B sending the data:
findNavController().previousBackStackEntry?.savedStateHandle?.set("key", data)
findNavController().popBackStack()
I also wrote extensions for this.
fun <T : Any> Fragment.setBackStackData(key: String,data : T, doBack : Boolean = true) {
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, data)
if(doBack)
findNavController().popBackStack()
}
fun <T : Any> Fragment.getBackStackData(key: String, result: (T) -> (Unit)) {
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<T>(key)?.observe(viewLifecycleOwner) {
result(it)
}
}
Usage:
Fragment A:
getBackStackData<String>("key") { data ->
// Do something with the data.
}
Fragment B:
setBackStackData("key",data)
Note: I am using String as data. You may use any other type of variable.
Note: If you are going to use a Class as data, don't forget to add #Parcelize annotation and extend Parcelable.
i have just came across the same problem and have several options i can propose (currently considering myself which ones is best for my use case).
create a navigation action from fragment C back to fragment A. note that if that's all you do, your stack will now be: A-B-C-A. to avoid this, you can use popUpTo and PopUpToInclusive. see the documentation.
the disadvantage of this method is that fragment A will be cleared no matter what, and would have to re-load (and fetch all its' data again).
use a shared ViewModel between for your fragments, and observe the required value with LiveData. in this scenario, fragment C will update a LiveData value in the shared ViewModel, which fragment A observes. now when you go back to fragment A, your observer will be triggered with the value set by fragment C.
similar to option 2, if you are using an external data source (like a Room database) you can observe changes using LiveData in fragment A. all fragment C has to do in this case is update the data source, and fragment A will automatically be updated through the LiveData

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

Retaining ViewModel in fragments

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)

Categories

Resources