ViewPager2 — digging the internal API to make it work with DiffUtil
In this article, I will not go into detail about ViewPager2, the difference between an old ViewPager, I will attach some links at the end of the article, in which this is described in detail. Here I will show an example of using ViewPager2 with mutable fragments collection with DiffUtil and what problems are hidden there.

ViewPager2
is an improved version of the ViewPager
library that offers enhanced functionality and addresses common difficulties when using ViewPager
. ViewPager2
has several advantages such as vertical orientation support, RTL and access to DiffUtil
. Because ViewPager2
is built on RecyclerView
and therefore has access to the benefits of DiffUtil
.
So, let’s cook our ViewPager2 with mutable fragments collection and DiffUtil (all code is available here).
The Problem
We want to make a ViewPager with fragments that contain data in themselves and this data can be changed over a period of time (for example, we have a socket connection and as soon as new data comes in, we want to update our ViewPager adapter). But we don’t want to update the whole list of fragments by calling notifyDataSetChanged()
, we want to benefit from DiffUtil
and update only those fragments, in which data has changed. To simplify example I will update data in each fragment manually by clicking the button.

Here we have our data class:
data class PagerItem(val id: Int, val title: String, val color: String, val value: Int)
value
is our data that will be changed. Then let’s quickly set up our adapter for ViewPager2. From the docs, we see that if we have a large or unknown number of fragments we need to use FragmentStateAdapter
.
Let’s jump into our Activity and setup ViewPager2
with our newly created adapter.
Now, when we run our app we can see that ViewPager2
is populated with a list of fragments.

But it doesn’t support any updates yet. So it’s time for DiffUtil
to come into play. As we know from the docs:
Note: The
DiffUtil
utility class relies on identifying items by ID. If you are usingViewPager2
to page through a mutable collection, you must also overridegetItemId()
andcontainsItem()
.
Also, Google provides g̵r̵e̵a̵t̵ examples of using ViewPager2
with different use cases including DiffUtil
usage example. Let’s update our adapter according to docs:
We’ve added two methods here, getItemId(int)
and containsItem(long)
, so they will help DiffUtil
to calculate the differences between our lists. As we know, DiffUtil
has some important methods that we have to correctly override. The first one is areItemsTheSame(int, int)
:
Called by the DiffUtil to decide whether two object represent the same Item.
For example, if your items have unique ids, this method should check their id equality.
The second one is areContentsTheSame(int, int)
:
Called by the DiffUtil when it wants to check whether two items have the same data. DiffUtil uses this information to detect if the contents of an item has changed.
And the last one which is optional to override is getChangePayload(int, int)
:
When
areItemsTheSame(int, int)
returns true for two items andareContentsTheSame(int, int)
returns false for them, DiffUtil calls this method to get a payload about the change.
With all that knowledge we can create our DiffUtil.Callback
:
For areItemsTheSame()
we used pagerItem.id
and since only one field of our object changes, we can use pagerItem.value
for method areContentsTheSame()
so if the value has changed then DiffUtil
will call getChangePayload()
where we can simply return hardcoded enum.
DiffUtil time
Now let’s update our adapter class and remove notifyDataSetChanged()
:
fun setItems(newItems: List<PagerItem>) {
val callback = PagerDiffUtil(items, newItems)
val diff = DiffUtil.calculateDiff(callback)
items.clear()
items.addAll(newItems)
diff.dispatchUpdatesTo(this)
}
Time to run the app and see the result:
Something went wrong. But is there a problem in our code, or not? onBindViewHolder(holder, int)
is not called for the currently visible item, but successfully gets called for the adjacent view to the right. Another important point is that when we swipe two screens further and then return to the first one, it successfully shows the updated value. ¯\_(ツ)_/¯.
But!
RecyclerView.Adapter
has another method called onBindViewHolder(holder, int, payloads)
.
Called by RecyclerView to display the data at the specified position. This method should update the contents of the
RecyclerView.ViewHolder.itemView
to reflect the item at the given position.
So… it means that we can override this method in our adapter and manually update RecyclerView.ViewHolder.itemView
in our case Fragment
to display new data. Let’s try it out:
override fun onBindViewHolder(
holder: FragmentViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (payloads.isNotEmpty()) {
// manually update a fragment
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
On debug, we can check whether this method is called on the currently visible item and therefore we can use it to somehow update our value on UI. But how to get currently visible fragment? FragmentViewHolder
doesn’t have currentFragment
field. ViewPager
has a method getCurrentItem()
, but ViewPager2
doesn’t have such API. But, if we look inside FragmentStateAdapter
we can find next lines of code:
mFragmentManager.beginTransaction()
.add(fragment, "f" + holder.getItemId())
.setMaxLifecycle(fragment, STARTED)
.commitNow();
Here we see that when FragmentStateAdapter
adds new fragment, it assigns a tag with the pattern "f" + holder.getItemId()
. How can this help us? Let’s try using this information to find our fragment in the onBindViewHolder(holder, int, payloads)
method.
The solution
In the code snippet above we firstly check for payloads then if it’s not empty we can try to find our fragment by using android’s internal tag. If the fragment is not null then we can safely cast it to our PagerFragment
and set the updated value.

Conclusion
And as a result of such machinations, everything started working as we wanted initially. This solution may look bad since we are using the internal API (and it could be changed at any time, so we should think twice before using it), but what else to do when there is no option out of the box (or is there?).
Useful links about ViewPager2 are below: