Removing/Adding fragments at index results in unexpected behaviour in Viewpager2. This was not possible with ViewPager
but expected to work with Viewpager2
. It causes duplicate fragments and out of sync TabLayout
.
Here is a demo project which reproduces this issue. There is a toggle button which removes a fragment and reattaches it at a particular index. In this case attached fragment should be green but it's blue and there are 2 blue fragments somehow.
here is how my adapter looks
class ViewPager2Adapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
val fragmentList: MutableList<FragmentName> = mutableListOf()
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return when (fragmentList[position]) {
FragmentName.WHITE -> WhiteFragment()
FragmentName.RED -> RedFragment()
FragmentName.GREEN -> GreenFragment()
FragmentName.BLUE -> BlueFragment()
}
}
fun add(fragment: FragmentName) {
fragmentList.add(fragment)
notifyDataSetChanged()
}
fun add(index: Int, fragment: FragmentName) {
fragmentList.add(index, fragment)
notifyDataSetChanged()
}
fun remove(index: Int) {
fragmentList.removeAt(index)
notifyDataSetChanged()
}
fun remove(name: FragmentName) {
fragmentList.remove(name)
notifyDataSetChanged()
}
enum class FragmentName {
WHITE,
RED,
GREEN,
BLUE
}
}
I have filed a bug with google as well
Turns out that you need to override these two methods if you are working with mutable
collections in ViewPager2
override fun getItemId(position: Int): Long {
return fragmentList[position].ordinal.toLong()
}
override fun containsItem(itemId: Long): Boolean {
val fragment = FragmentName.values()[itemId.toInt()]
return fragmentList.contains(fragment)
}
Adding these two in my current adapter fixes the problem
This is weird on my side overriding
getItemId()
containsItem()
will just give undesirable behavior when using 3 kinds of Fragments.
In the end all I need was a simple FragmentStateAdapter
class like this
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()
}
}
Too bad I search for an immediate answer on how to make ViewPager2 dynamic without giving a shot first on the simplest approach I could come up. Many answer here on SO pointing out that getItemId()
and containsItem()
needs to be override when adding or removing Fragment(s) on ViewPager2 which gives some headache for almost 2 days. Felt betrayed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With