Get same instance viewModel from nested Fragments - android

I have a layout which is like that
MainActivity has a NavHost with 3 Fragments
SitesFragment, GroupsFragment, GalleryFragment
GalleryFragment has a ViewPager with 3 Fragments (Image, Audio, Video)
GalleryFragment has a GalleryViewModel
private val galleryViewModel: GalleryViewModel by viewModels()
So inside the ImageFragment i want to get the SAME INSTANCE of GalleryViewModel
private val galleryViewModel: GalleryViewModel by viewModels({ requireParentFragment() })
I tried to get it through the parentFragment but it is not the same instance!
How can i do that?

You can use activityViewModels() rather than viewModels() so your view models are provided by the activity hosting your fragments. https://developer.android.com/kotlin/ktx#fragment

Related

Same fragment with different navgraph by hiltNavGraphViewModels

I followed single activity pattern for my app. My MainActivity has a bottom navigation with 4 fragments.
nav_home and nav_video have same fragment so I want to HomeViewModel and VideoViewModel keep their states within these graphs. As you can see below.
nav_home.xml
nav_video.xml
On my fragment I use hiltNavGraphViewModels extension to keep viewmodel states.
#AndroidEntryPoint
class CourseDetailFragment : Fragment(R.layout.fragment_course_detail),
LoadingDelegate by LoadingDelegateImpl(), NavigationDelegate by NavigationDelegateImpl() {
private val binding by viewBinding(FragmentCourseDetailBinding::bind)
private val viewModel: CourseDetailViewModel by viewModels()
private val categoryViewModel: CategoryViewModel by hiltNavGraphViewModels(R.id.nav_video)
private val homeViewModel: HomeViewModel by hiltNavGraphViewModels(R.id.nav_home)
If I reach this fragment from home, categoryViewModel throws exception and it says:
java.lang.IllegalArgumentException: No destination with ID 2131362218
is on the NavController's back stack. The current destination is
Destination(com.example.app:id/courseDetailFragment)
label=CourseDetailFragment
class=com.example.app.ui.course.CourseDetailFragment
On the other hand, if I reach this fragment from video, homeViewModel throws same exception.
Any help would be most welcomed. Thanks.
Maybe you can use this:
private val sampleViewModel: SampleViewModel by hiltNavGraphViewModels(findNavController().graph.id)
However in my case, the vm is lazy initialized even before onCreate() and findNavController() is not yet available at that time.
Here's what I did:
fun provideVM(): SampleVM =
hiltNavGraphViewModels<SampleVM>(findNavController().graph.id).value
provideVM() is called during onViewCreated()

Sharing ViewModel in ViewPager using parent fragment as owner

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.

How to get parent Fragment from ViewPager Fragment that is an AndroidEntryPoint?

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.

Hilt Create one view model instance shared between the activity and their 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()

Using same viewmodel on different fragments

I have viewModel on Fragment A, which I load in this way:
viewModel = ViewModelProvider(this, viewModelFactory).get(AFragmentVM::class.java)
then from fragment A, I go to fragment B. Is this possible to use the same viewmodel on fragment B? In Fragment B I tried (as in docs):
private val viewModel: AFragmentVM by activityViewModels()
but I get an exception while trying to use this ViewModel:
java.lang.RuntimeException: Cannot create an instance of class ...AFragmentVM
...
BFragment.getViewModel(Unknown Source:2)
BFragment.onCreateView(ChartFragment.kt:40)
...
Caused by: java.lang.NoSuchMethodException: ...AFragmentVM.<init> [class android.app.Application]
EDIT:
Based on #SebastienRieu and #IntelliJ Amiya answers, everything that I had to do was to create ViewModel on Fragment A in this way:
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(AFragmentVM::class.java)
Or:
viewModel = ViewModelProvider(let {activity}!!,viewModelFactory).get(AFragmentVM::class.java)
Then on Fragment B I could use:
private val viewModel: AFragmentVM by activityViewModels()
If the two fragments are in the same activity you should replace this :
viewModel = ViewModelProvider(this, viewModelFactory).get(AFragmentVM::class.java)
by this
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(AFragmentVM::class.java)
and add a viewModel in the activity and initialize it in this activity like this :
viewModel = ViewModelProvider(this, viewModelFactory).get(AFragmentVM::class.java)
With requireActivity() when setup the VM in fragment you tell the fragment to use the activity shared ViewModel
Fragment A
viewModel = ViewModelProvider(let {activity}!!,viewModelFactory).get(AFragmentVM::class.java)
Fragment B
private lateinit var viewModel: AFragmentVM

Categories

Resources