FragmentStateAdapter does not call "createFragment" when its "notifyDataSetChanged" function is called

3.8k views Asked by At

I am using a ViewPager2 together with an adapter that minimally extends FragmentStateAdapter, as follows:

class MyAdapter(fragment: Fragment): FragmentStateAdapter(fragment) {

    override fun createFragment(position: Int): Fragment {
        return MyFragment.newInstance(position)
    }

    override fun getItemCount(): Int {
        return 5
    }
}

Unfortunately, calling notifyDataSetChanged() on the adapter has no effect at all. (Calling notifyItemChanged(position) works fine.)

How can I solve this?

4

There are 4 answers

1
Adil Hussain On

I've created a bug report for this here in IssueTracker. For me, though, even the notifyItemChanged(position:) and notifyItemRangeChanged(positionStart:itemCount:) methods behave incorrectly.

The bug is due to the default implementation of the FragmentStateAdapter.getItemId(position:) method which simply returns the position passed into it. The itemId for each position will therefore be the same before and after a FragmentStateAdapter.notifyDataSetChanged() call and therefore none of the Fragments will ever be recreated.

One possible workaround is to assign each page a unique id within your FragmentStateAdapter implementation and to assign a new id to each page whenever the notifyDataSetChanged() method is called. See the init block in the ViewPagerAdapter class below which facilitates this workaround: An AdapterDataObserver object is registered in this init block such that it receives a callback whenever notifyDataSetChanged() is called.

class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
    FragmentStateAdapter(fragmentActivity) {

    private var counter = 0L
    private var itemIds: List<Long>

    init {
        itemIds = generateListOfItemIds()

        val adapterDataObserver = object : AdapterDataObserver() {

            override fun onChanged() {
                itemIds = generateListOfItemIds()
            }
        }

        registerAdapterDataObserver(adapterDataObserver)
    }

    override fun createFragment(position: Int): Fragment {
        return PageFragment.newInstance(position)
    }

    override fun getItemCount(): Int {
        return itemIds.size
    }

    override fun getItemId(position: Int): Long {
        return itemIds[position]
    }

    override fun containsItem(itemId: Long): Boolean {
        return itemIds.contains(itemId)
    }

    private fun generateListOfItemIds() = (1..5).map { counter++ }
}
1
Da Chelimo On

My situation was using ViewPager2 and TabLayout in a Fragment. I was trying to pass LiveData from the current main Fragment to the Fragments in the ViewPager2.

DailyTabFragment.kt

class DailyTabFragment {
    lateinit var timePeriod: LiveData<TimePeriod>

    companion object {
        fun getInstance(
            selectedTimePeriod: LiveData<TimePeriod>
        ) =
            DailyTabFragment().apply {
                timePeriod = selectedTimePeriod
            }
    }
}

There were times my Fragment was calling onCreateView without any value. Therefore, whenever I used this LiveData, I would get an UninitializedPointerException. The solution was observing the LiveData only if it has been initialized.

if (::timePeriod.isInitialized) {
    timePeriod.observe(viewLifecycleOwner) {
        // Do some stuff....
    }
}
2
Aitha Ranjeeth On

Before calling setAdapter(adapter:) on your ViewPager2 object, call its setSaveFromParentEnabled(enabled:) method with a value of false, i.e. viewPager.setSaveFromParentEnabled(false).

1
Andrew Evtukhov On

Just override the getItemId(position: Int): Long function on FragmentStateAdapter.