I need to add fragments to viewpager2 as well as tabs at runtime depending on server config file. Most viewpager2 resources I have seen showcase a static viewpager2 using the new TabMediator Tab layout. I am skipping viewpager as I need to add RTL and vertical scrolling supported by viewpager2.
My biggest concern is how to I track the index of a specific page/fragment when I add and remove fragments. Have gone through this tutorial and code as an exapmple.
This is how you implement it in Kotlin
viewPager = binding.viewPagerContainer
val tabLayout : TabLayout = binding.tabLayout
val fragmentList : MutableList<Pair<String, Fragment>> = mutableListOf()
fragmentList.add(Pair(getString(R.string.assets), AssetFragment.newInstance()))
fragmentList.add(Pair(getString(R.string.news), NewsFragment.newInstance()))
val adapter = AppFragmentAdapter(fragmentList, this)
Handler(Looper.myLooper()!!).postDelayed({
adapter.addFragment(Pair(getString(R.string.videos), VideosFragment.newInstance()))
}, 1000)
Handler(Looper.myLooper()!!).postDelayed({
adapter.removeFragment(2)
}, 2000)
viewPager.adapter = adapter
viewPager.offscreenPageLimit = 2
val layoutInflater : LayoutInflater = LayoutInflater.from(context)
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val actionBar : ActionBar = (requireActivity() as AppCompatActivity).supportActionBar!!
actionBar.title = adapter.getFragmentName(position)
}
})
TabLayoutMediator(tabLayout, viewPager){ tab, position ->
tab.customView = prepareTabView(layoutInflater, tabLayout, adapter.getFragmentName(position), tabIcons[position])
}.attach()
Here we already tested to add and remove the last fragment using some basic delay upon launching an Activity or Parent Fragment. You can create a custom view for each tab or just use tab.text = "Tab Name" for simplicity.
Now the type of adapter you will be using with ViewPager2 for Fragment is FragmentStateAdapter
class AppFragmentAdapter(private val fragmentList: MutableList<Pair<String, Fragment>>, fragment: Fragment) : FragmentStateAdapter(fragment) {
// private var pageIds = fragmentList.map { fragmentList.hashCode().toLong() }
override fun getItemCount(): Int = fragmentList.size
override fun createFragment(position: Int): Fragment {
return fragmentList[position].second
}
// override fun getItemId(position: Int): Long = pageIds[position] // Make sure notifyDataSetChanged() works
// override fun containsItem(itemId: Long): Boolean = pageIds.contains(itemId)
fun getFragmentName(position: Int) = fragmentList[position].first
fun addFragment(fragment: Pair<String, Fragment>) {
fragmentList.add(fragment)
notifyDataSetChanged()
}
fun removeFragment(position: Int) {
fragmentList.removeAt(position)
notifyDataSetChanged()
}
}
If your structure is Parent Fragment > ViewPager2 > Child Fragments the above code will work fine.
If your structure is Activity > ViewPager2 > Fragments just change
class AppFragmentAdapter(private val fragmentList: MutableList<Pair<String, Fragment>>, fragment: Fragment) : FragmentStateAdapter(fragment)
to
class AppFragmentAdapter(private val fragmentList: MutableList<Pair<String, Fragment>>, fragment: FragmentActivity) : FragmentStateAdapter(fragment)
then instead of
val adapter = AppFragmentAdapter(fragmentList, this)
pass the Fragment's activity like this
val adapter = AppFragmentAdapter(fragmentList, requireActivity())
Internally FragmentStateAdapter(fragment)
from our adapter already handle which FragmentManager is supposedly use.
Activity > ViewPager2 > Fragments
public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
}
Parent Fragment > ViewPager2 > Child Fragments
public FragmentStateAdapter(@NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
I was deceived first by searching in SO about dynamically add/remove Fragment with ViewPager2 before trying the simplest approach I could come up.