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

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.

Related

How to Share data between a parent and child fragment android Kotlin

I have fragment which contain two dialog fragments i.e (Fragment A -> (Navigate) -> (Dialog A, DialogB) I want to share data between these fragments I tried this way mentioned in developer android Share data between a parent and child fragment
class ListFragment: Fragment() {
// Using the viewModels() Kotlin property delegate from the fragment-ktx
// artifact to retrieve the ViewModel
private val viewModel: ListViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
// Update the list UI
}
}
}
class ChildFragment: Fragment() {
// Using the viewModels() Kotlin property delegate from the fragment-ktx
// artifact to retrieve the ViewModel using the parent fragment's scope
private val viewModel: ListViewModel by viewModels({requireParentFragment()})
...
}
When I use it in my case is not working and the views in the parent fragment are not changing when I modify data in the child fragment Also when I logged the view models I found that each view model have its own Id as shown below. Any way to deal with this problem or clear explanation to this.

should I use newInstance function somewhere?

I'm new in kotlin and android development.
I was following a tutorial, in that tutorial in a fragment class, companion object, defined a method named newInstance() that returned a fragment, the method was never used.
class myFragment : Fragment(){
companion object {
fun newInstance(foo:Int): myFragment {
val fragment = myFragment()
val args = Bundle()
args.putString("foo", foo)
fragment.arguments = args
return fragment
}
}
}
Is that okay?
Is that going to call it automatically or should I call it somewhere?
(sorry if the explanation isn't good)
It not gonna be used automatically, it is just one of the ways to create fragments.
Basically you need to call this function in the place where you wish to add/ replace this fragment into it's container with the help of FragmentManager
You need to use supportFragment manager in your activity to replace the fragment
val transition = supportFragmentManager.beginTransaction()
transition.addToBackStack("Your_fragment_unique_tag")
transition.replace(containerViewId, fragment).commit()
containerViewId will be FrameLayout id in your activity which is the container for replacing fragment i.e R.id.mainContainer

Fragment instances are kept in viewPager2 when host fragment removed

Have one activity app, where I have HomeFragment from which I open HostFragment, and HostFragment has ViewPager2 with 3 fragment items TabFragment1, TabFragment2 and TabFragment3.
When I open the HostFragment, and then go back, my tabFragments' instances are not removed. Below is the logs from lifecycles
navController.navigate(directionToHostFragment)
HostFragment OnStart HostFragment{c37542c}
TabFragment1 OnStart TabFragment1 {f41b08}
navController.popBackStack()
HostFragment OnPause HostFragment{c37542c}
HostFragment OnStop{c37542c}
HostFragment OnDestroy {c37542c}
navController.navigate(directionToHostFragment)
Blockquote
HostFragment OnStart{2670c03}
TabFragment1 OnStart{f45b87d}
navController.popBackStack()
HostFragment OnPause{2670c03}
HostFragment OnStop{2670c03}
HostFragment onDestroy{2670c03}
From ids you can see that there is a new TabFragment1 instance and old one haven't destroyed(I have logs in onPause(), onStop() etc.).
Some code snippets:
Adapter for viewPager2 I used -
class LessonTabsAdapter(fragmentActivity: FragmentActivity, val pages: List<BaseFragment>) :
FragmentStateAdapter(fragmentActivity) {
override fun getItemCount() = pages.size
override fun createFragment(position: Int) = pages[position]
}
Some part from TabFragment1
class TabFragment1 : BaseFragment(R.layout.fragment_tab_1) {
private var mediaPlayer: MediaPlayerClass? = null
private lateinit var player: SimpleExoPlayer
private val viewModel by sharedViewModel<SomeViewModel>()
private val binding by viewDataBinding(FragmentTab1Binding::bind)
//Overriden onViewCreated(..) and some private methods
}
So any hints how to deal with this problem?
When you open the HostFragment the ViewPager2 creates a fragment in the lifecycle provided. In this case you are passing activity as the parameter so, it is creating the fragments in Activity's lifecycle. You can check the same by printing the activity?.supportFragmentManager?.fragments list. If you want to couple the ViewPager2's childs with HostFRagment, you need to pass the fragments instance to the FragmentStateAdapter. Check the function 2 that I have added. The best way to implement the ViewPager2 is by using function 3.
1..
public FragmentStateAdapter(#NonNull FragmentActivity fragmentActivity) {
this(fragmentActivity.getSupportFragmentManager(),fragmentActivity.getLifecycle());
}
2.//
public FragmentStateAdapter(#NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
3.//
public FragmentStateAdapter(#NonNull FragmentManager fragmentManager, #NonNull Lifecycle lifecycle) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}

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.

Get same instance viewModel from nested Fragments

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

Categories

Resources