I have a parent Fragment A with Fragment B and Fragment C inside a ViewPager. I want to share Fragment's A ViewModel within Fragment B and Fragment C.
This was my approach which seemed intuitive at first:
In parent Fragment A which contains ViewPager, I create my viewModel like this:
val viewModel by viewModels<SharedViewModel>()
...
#HiltViewModel
class SharedViewModel #Inject constructor(
stateHandle: SavedStateHandle
) {
val myArgument = stateHandle.get<Boolean>("myArgument")
...
}
My ViewPager Fragment's B and C are trying to access same SharedViewModel instance like this:
private val sharedViewModel by viewModels<SharedViewModel>(
ownerProducer = { requireParentFragment() }
)
Everything works fine, except when the process is destroyed, my savedStateHandle in the SharedViewModel does not retain arguments passed by navigation component. Doing this in SharedViewModel returns null and savedStateHandle is empty.
val myArgument = stateHandle.get<Boolean>("myArgument")
Does someone know why is this happening? Using activityViewModels seems to fix this, but I do not fully understand why.
Related
I have a Fragment within a ViewPager that is created like this:
companion object {
fun newInstance(someData: SomeData): ViewPagerFragment {
val f = ViewPagerFragment()
val args = Bundle()
args.putParcelable("someData", someData)
f.arguments = args
return f
}
}
It is also a Hilt entry point, and it creates its own ViewModel, but also needs the parent Fragments ViewModel, so the top of the class looks like this:
#AndroidEntryPoint
class ViewPagerFragment : Fragment() {
private val parentViewModel: ParentViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
private val viewPagerViewModel: ViewPagerViewModel by viewModels()
However, the parent Fragment is not found:
java.lang.IllegalStateException: Fragment ViewPagerFragment{b6d6157}
(e5f3fc7d-2ae4-4275-9763-22826a9be939) id=0x7f09017e} is not a child Fragment, it is
directly attached to
dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper#1852444
I think this is happening because after adding the viewPagerViewModel the generated ViewPagerFragment class is not a childFragment. Is there a workaround to get the parent Fragment and ultimately get the parent ViewModel? The latter is the main goal.
For now I use ViewPager, not ViewPager2, because it's a legacy infinite wrapper ViewPager.
EDIT:
The ViewPagerAdapter is created like this:
val pagerAdapter = VpPagerAdapter(
requireActivity().supportFragmentManager,
someData,
)
When you create your ViewPager adapter, you need to pass in the childFragmentManager if you want the fragments in the ViewPager to be considered child fragments.
Iam trying to create shared view model between activity and the fragments.
In the activity :
val viewModel: SharedViewModel by viewModels()
And in fragments:
val viewModel: SharedViewModel by navGraphViewModels(R.id.activity_nav_graph) {
defaultViewModelProviderFactory
}
But 2 instances currently created one on the activity and one in the fragments
in your fragment should be
private val viewModel: SharedViewModel by activityViewModels()
I have created an instance of ViewModel in MainActivity and setup an observer. I want the observed data into one of the fragments of MainActivity's ViewPager. How can I get the required LiveData into the fragment.
Using AndroidX extension delegate
In the MainActivity:
private val activityViewModel: SomeViewModel by viewModels()
In the Fragement
private val activityViewModel: SomeViewModel by activityViewModels()
With ViewModelFactory, put the ViewModelFactory intance into closure
private val activityViewModel: SomeViewModel by viewModels{ viewModelFactory }
private val activityViewModel: SomeViewModel by activityViewModels{ viewModelFactory}
you can use shared ViewModel
Shared ViewModel between activity and fragments
for this you can define an object from activity's viewModel in your fragment with activity as viewModel's owner and apply changes on that's variables.
I'm using a shared view model like here
But the problem is that when I clear my last fragment, I want to clear the viewmodel, or kill its instance, but somehow it survives when I leave the last fragment that uses it
How can I programatically clear this viewmodel ?
I use it like this
Fragment A
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated() {
model.getTotal().observe(viewLifecycleOwner, Observer { cartTotal ->
total = cartTotal
})
}
From fragment B I sent the total
Fragment B
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated() {
model.setTotal = 10
}
But when leaving Fragment A with that data (doing popBackStack since I'm using navigation components) it does not clear the viewmodel, instead when I open again my fragment , the data stills there
I suspect that the viewmodel is tied with my Container Activity and not the lifecycle of the fragments itself, so
How can I remove the instance or clear my viewmdel when I hit my last fragment ?
Thanks
If you want to get a ViewModel associated with a parent fragment, your inner fragment should follow the by viewModels JavaDoc and use:
val viewmodel: MYViewModel by viewmodels ({requireParentFragment()})
This says to use the parent Fragment as the owner of your ViewModel.
(The parent fragment would use by viewModels() as it is accessing its own ViewModels)
you can also clear viewModelStore manually after Fragment A destroyed.
something like this :
override fun onDetach() {
super.onDetach()
requireActivity().viewModelStore.clear()
}
then your viewModel instance will be cleared. for checking this work you can debug onCleared method of your viewModel.
I'm trying to share a ViewModel between my activity and my fragment. My ViewModel contains a report, which is a complex object I cannot serialize.
protected val viewModel: ReportViewModel by lazy {
val report = ...
ViewModelProviders.of(this, ReportViewModelFactory(report)).get(ReportViewModel::class.java)
}
Now I'm trying to access the viewmodel in a fragment, but I don't want to pass all the factory parameters again.
As stated by the ViewModelProvider.get documentation:
Returns an existing ViewModel or creates a new one in the scope
I want to access the ViewModel instance defined in the activity, so I tried the following but it logically crashes as the model doesn't have an empty constructor:
protected val viewModel: ReportViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(ReportViewModel::class.java)
}
How one should access its "factorysed" ViewModels in a fragment? Should we pass the factory to the fragment?
Thanks!
A little late but I had this question myself. What I found is you can do the following:
In your activity override getDefaultViewModelProviderFactory() like so:
override fun getDefaultViewModelProviderFactory(): ReportViewModelFactory {
return ReportViewModelFactory(report)
}
now in your fragments you can do
requireActivity().getDefaultViewModelProviderFactory()
to get the factory.
Or simply instantiate your viewModel like:
private val viewModel: ReportViewModel by activityViewModels()