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);
}
Related
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
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
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.
I saw many people use fragmentManager for activity to fragment call. So I tried that too. When I click toolbar first, I got the error like this.
kotlin.UninitializedPropertyAccessException: lateinit property mActivity has not been initialized
even though I override and initialize that method, can't see the log when starting onAttach. Why I can't initialize activity? Myfragment is DaggerFragment.
Main button click
binding.toolbar.setOnClickListener {
currentFramgnet = MainPictureFragment().instance
changeFragment(this#MainActivity, binding.fragmentContainer, currentFramgnet!!)
(currentFramgnet as MainPictureFragment).changeDataset("button OnClick")
}
Main changeFragment
fun changeFragment(activity: AppCompatActivity, view: View, fragment: Fragment) {
val fragmentManager = activity.supportFragmentManager
fragmentManager
.beginTransaction()
.replace(view.id, fragment)
.commit()
}
Fragment changeDataset
fun changeDataset(mes : String){
showLog("changeDataset : "+mes)
showToast(mActivity,"11111")
}
Fragment onAttach
private lateinit var mActivity :Activity
override fun onAttach(context: Context) {
super.onAttach(context)
showLog("onAttach")
if (context is Activity) mActivity = context
}
updated : I logged this and found that the changeDataset is called before onAttach()
D/TAG: changeDataset : button OnClick
D/TAG: onAttach
Your problem is you try to access Context before fragment transaction execute because you use commit(). Easy way to fix it is using commitNow().
Is there an efficient way to automatically set the toolbar's title when adding/replacing fragments as well as popping fragments from the backstack?
I have implemented this abstract method in my BaseFragment class:
abstract fun header() : String
override fun onResume() {
super.onResume()
(activity as SSBaseActivity).header.text = header()
}
and I modify the header in each Fragment that inherits from my BaseFragment class and displays the value in onResume but I noticed that when I press back, the last title set isn't being replaced from the fragment currently in the stack.
You could do this by using an OnBackStackChangedListener in your Activity:
supportFragmentManager.addOnBackStackChangedListener {
val fragment = supportFragmentManager.findFragmentById(R.id.yourFragmentFrame)
if (fragment is BaseFragment) {
header.text = fragment.header()
}
}