PagedList in ViewPager

Or how to share common PagedList between different types lists

Recently I faced with a next test task:

You need to write a test app, that has 2 pages. On the first page there is a search bar, where you can write something. When you finished an press appropriate button, you need to load images from Flickr service and show them. When you click on image, a second screen with clicked photo is shown.

So during execution of this test task, I thought “It would be nice to have an ability of swiping photos in a second page, because it will help user to not go back every time, when he wants to see full size photo”.

I thought, it is a great idea, but then I started to think, how to implement it. You need to not only pass list of photos instead of one photo, but you need also give an opportunity for second page to download a new portion of images every time user nearly to the end of the list.

For the first page I’ve used a recently presented . Not only because it handy, but also because of my curiosity and a wish to try something new. And I thought “Could we just reuse this mechanism for ?”.

I didn’t find any solutions in the internet and there was no such adapter in the android paging library, so I’ve decided to write the simplified version of this one.

First version

Suppose, that we have an instance of and we just need to make an adapter for it, with ability of downloading nearest items. So our first version of adapter will approximately look like:

class FullSizePhotoAdapter(
val photos: PagedList<Photo>,
) : PagerAdapter() {

override fun isViewFromObject(view: View, obj: Any) = view == obj

override fun getCount() = photos.size

override fun
instantiateItem(container: ViewGroup, position: Int): Any {
val root = LayoutInflater.from(container.context).inflate(R.layout.item_full_screen_photo, container, false)

photos.loadAround(position)

... //prepare your view to be shown

return root
}

override fun destroyItem(container: ViewGroup, position: Int, obj: Any) =
container.removeView(obj as View)
}

Here you can see a full example of such adapter. So, as you can see, we just try to load a photos ‘around’ if we can on every instantiation of item. Internally method, roughly speaking, just takes passed and — count of items that need to be fetched (you can specify it in instance like here, if it is not set, then it will be equal to setting). Then method just checks is last loaded item more than , and if it is, then it loads new page of items.

That’s why we don’t need to worry about too often calls. If will be higher than current last loaded index, then no downloading will happen.

What’s wrong with this solution

As for me, this solution is pretty obvious, concise (just one line) and effective, so it covers most of cases, but it has one big (for my task) flaw:

Infinite loading effect until you manually swipe left/right

I have with placeholders, so if item is not loaded now, I need just to show loading placeholder instead of actual item, but when its loading will be finished, I need update currently visible item with loading state to present user a loaded image. But in a gif above you can see, that item is not shown, until you swipe (to see that effect clearly you need just set page size in to 1 or 2 and then start rapidly swiping items).

So how we can resolve that?

Sharing PagedList

But first I want to digress and tell about sharing . In my test app I had two activities. In the first one I showed a list of images on , which is stored and partially loaded by and presented by PagedListAdapter inheritor. In the second one I showed a list of full screen images on , which is stored also by PagedList and presented by PagerAdapter inheritor.

And the logic question, could we share same ? Yes, but we need to remember few things.

  1. store to its callbacks, which can be, for example, . So we need to be sure that our instance of has appropriate callbacks every time to avoid memory leaks.
  2. does not have to save and restore its state, since it has storage, and thus the ability to re-download items.

Final Version

Now we can re-think both paragraphs and formulate two important wishes:

  1. We want to update ViewPager when currently visible item is loaded
  2. We want to support subscribing and unsubscribing callbacks

Those statements lead us to next snippet of code:

Here we can see, that and methods are used to determine, which pages are now visible, and which are gone. Positions passed through method are stored in which formulates window of currently visible items.

When we set a new list to adapter, first, we remove inner callback from the old one and then set this callback to the new one. In this callback we receive the information about inserted, removed or updated ranges of data. Here we analyze and compare those ranges with our window of currently visible items and understand do we need to call or not.

Note that as we have shared PagedList between two activities (by service locator, or kodein or other DI frameworks), we need to remove adapter’s inner callback, when we finished work with activity, so we need to call afterwards.

Conclusion

is a great architecture component added recently. And I think its very powerful. Moreover it flexible enough to be built in 's system.

If you want to see the whole project and play with it, you can visit this repository. Hope you enjoyed. :)

Love being creative to solve some problems with an simple and elegant ways