Loading...
Home
  • Tech Blogs
  • Videos
  • Conferences
    • Droidcon News
    • Upcoming Conferences
    • Become a Partner
    • Past Events
    • Keep Me Informed
    • Diversity Scholarships
  • Community
    • droidcon Team
    • How to Hold a Droidcon
  • Android Careers
Sign In

Global CSS

 

ViewPager2 — digging the internal API to make it work with DiffUtil

 

 
Ruslan Myhal
Android engineer
Published: August 11, 2020
Tweet
Share

 

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.

 

Image by exxenia

 

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.

 

Image for post

What the page looks like

 

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.

 

PagerAdapter.kt

 

Let’s jump into our Activity and setup ViewPager2 with our newly created adapter.

 

MainActivity.kt

 

Now, when we run our app we can see that ViewPager2 is populated with a list of fragments.

 

Image for post

 

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 using ViewPager2 to page through a mutable collection, you must also override getItemId() and containsItem().

 

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:

 

 

Updated PagerAdapter.kt

 

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 and areContentsTheSame(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 :

 

PagerDiffUtil.kt

 

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.itemViewto 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 Fragmentto 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.

 

onBindViewHolder(holder, int, payloads)

 

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.

 

Image for post

 

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:

  • About ViewPager2 by serap bercin
  • Exploring the ViewPager2 by Joe Birch
  • Migration guide from ViewPager to ViewPager2 by Google

 

 

rmyhal/ViewPager2-DiffUtil

Example of using ViewPager2 with mutable fragments collection powered by DiffUtil GitHub is home to over 50 million…

github.com

 

 

Tags: Android,Viewpager2,Diffutil,Android App Development,Android Jetpack

 

View original article at: 


 

Originally published: July 28, 2020

Android News
Getting… your BottomSheetScaffold working on Jetpack Compose Beta 03
Getting… your BottomSheetScaffold working on Jetpack Compose Beta 03

By Carlos Mota

It’s Monday, no releases this week, and… there’s a new version of Jetpack Compose — beta 03—available. What a perfect time to just increment 02 to 03 and see what’s new. The API is (almost) final so after updating from alpha to beta there weren’t any big changes to do. However, and remember that’s still in development, there’s always something that I need to update. 

By ProAndroidDev -
Android News
Noisy Code 🗣 With Kotlin Scopes
Noisy Code 🗣 With Kotlin Scopes

By Chetan Gupta

Scopes make your code more readable? think again... You are going to encounter these scope functions namely let, run, apply, also, within every Kotlin codebase, along with all the mischievous ways developers exploit their usage from the way they were intended for. Let see how popular opinion on those ends up just as a code noise.

By ProAndroidDev -
Android News
Improving Android DataBinding with Bindables library
Improving Android DataBinding with Bindables library

By Jaewoong Eum

DataBinding is one of the most important factors for MVVM architecture. The basic concept of DataBinding is to link the view and view model via observer patterns, properties, event callbacks, etc. Linking and automating communication between the view via the bound properties or something in the view model has a lot of benefits in the MVVM architecture concept.

By ProAndroidDev -
Android News
KMM QuickStart Guide
KMM QuickStart Guide

By Mayank Kharbanda

Kotlin Multiplatform (KMP) is a code-sharing technology that allows us to use the same code for different platforms whether it’s JVM, Android, iOS, watchOS, tvOS, Web, Desktop, or WebAssembly. In this article, we will use Kotlin Multiplatform Mobile (KMM) which is a subset of KMP with the focus on providing better tooling and support for sharing code on mobile platforms i.e. Android and iOS.

By ProAndroidDev -
droidcon News

Tech Showcases,

Developer Resources &

Partners

/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/home-details/EmployerBrandingHeader
EmployerBrandingHeader
https://jobs.droidcon.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/jobs-droidcon/jobs.droidcon.com
jobs.droidcon.com

Latest Android Jobs

http://www.kotlinweekly.net/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/kotlin-weekly/Kotlin Weekly
Kotlin Weekly

Your weekly dose of Kotlin

https://proandroiddev.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/pad/ProAndroidDev
ProAndroidDev

Android Tech Blogs, Case Studies and Step-by-Step Coding

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
Zalando

Meet one of Berlin's top employers

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
Academy for App Success

Google Play resources tailored for the global droidcon community

Follow us

Team droidcon

Get in touch with us

Write us an Email

 

 

Quicklinks

> Code of Conduct

> Terms and Conditions

> How to hold a conference

> FAQs

> Imprint

Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.

powered by Breakpoint One