Blog Infos
, ,

Following Paras Singh Sidhu’s blog on scoped storage, and how it can be used in Android, was very helpful in understanding the intricacies of the MediaStore API in Android.

This is a heavy task and should be done on a background thread. Kotlin Coroutines should make this task very easy.

The above line in the aforementioned blog got me thinking, can we improve the performance of the RecyclerView even more?

And the not-so-surprising answer was, yes we can.

In this blog we will read about how one can implement the API with a RecyclerView, using pagination. Make sure that you have read the blog mentioned above.

Your Android device might contain thousands of images. To my surprise, my device had over 5000 images, and getting the images in a list and adding them to the RecyclerView was a heavy task, and slowed down the application.

To solve this, we use pagination, which loads only some images at a time, and keeps on loading them as and when required.

Note: In this blog, I will not explain which permissions need to be fetched and how.

Creating the MVVM structure

Let us first start by creating the structure for MVVM, that is Repo and ViewModel classes.

DocumentRepo: This is the repository class that we make. This class would contain the core logic behind pagination.

DocumentViewModel: The ViewModel class contains the live data to observe the images data.


As you have already seen how we create the cursor, idColumn, dateModifiedColumn, and the displayNameColumn.

Now create a suspend function with this code:

suspend fun getImages(count: Int, start: Int): Three<MutableList<Three<Uri?, String?, Date>>, Boolean, Int> {
val imagesList = mutableListOf<Three<Uri?, String?, Date>>()
var index = start
return withContext(Dispatchers.IO) {
while (imageCursor?.moveToPosition(i) == true) {
val id = imageIdColumn?.let { imageCursor.getLong(it) }
val dateModified = Date(TimeUnit.SECONDS.toMillis(imageCursor.getLong(imageDateModifiedColumn ?: 0)))
val displayName = imageDisplayNameColumn?.let { imageCursor.getString(it) }
val contentUri = id?.let {
contentUri?.let { imagesList.add(Three(contentUri, displayName, dateModified)) }
if (index == count)
val areAllLoaded = index == imagesToLoad
return@withContext Three(imagesList, areAllLoaded, index)
view raw documentRepo.kt hosted with ❤ by GitHub

In the above function, we first create a local list variable to store the images data.

The moveToPosition(index) function moves the cursor to an absolute position and returns a boolean if data exists at the position. We start indexing from index 0 (the initial value of start is 0), and we go on till the last index.

As you can see that we have maintained a count variable, which specifies how many images we want to pick at a time.

It can be seen that the return type of the function is a Mutable List of Three 😳.

This is a class I created that resembles Pair (Kotlin), but lets you add three values instead of just two.

data class Three<A,B,C>(var a: A, var b: B, var c: C)

Job Offers

Job Offers

    Senior Android Engineer – Big Release Team

    Zalando SE
    • Full Time
    apply now

    Delivery Lead / Scrum Master (m/w/d)

    Deutsche Post IT Services (Berlin) GmbH
    • Full Time
    apply now

    Android Developer

    Yoti Ltd
    • Full Time
    apply now
Load more listings


, , ,

Migrating to Paging Library

The paging library offers a complete solution for efficiently displaying large lists of data but adopting such a comprehensive library can be daunting. In this talk, I’ll cover some of the lessons we learned while gradually migrating from a RecyclerView.Adapter, populated from a tightly coupled domain and presentation layers, to Paging library.


Note: Kotlin also contains a Triple class.

We store the image Uri, Display Name, and the date in the list.

The function returns the image list that we create, areAllLoaded which tells if we can load more images, and index, which is the next index from which we shall start indexing.


This class contains code that sets values to LiveData and takes care if we have loaded all images:

private val _imagesLiveData = MutableLiveData<MutableList<Three<Uri?, String?, Date>>>()
val imagesLiveData: LiveData<MutableList<Three<Uri?, String?, Date>>>
get() = _imagesLiveData
private var start = 0
private var areAllLoaded = false
fun getImages(count: Int) {
if (areAllLoaded)
viewModelScope.launch {
val response = documentRepo.getImages(count, start)
start = response.c
areAllLoaded = response.b
val data = response.a

In the above code, we return the function if all the images are loaded, else we get the new images starting from the last index, and specify the number of images that we wish to fetch (count).

Creating the Activity

We will now create an instance of the DocumentViewModel inside our activity, and set up the RecyclerView as follows.

mediaRecycler.apply {
    layoutManager = gridLayoutManager
    adapter = localDocumentsAdapter

The value of item_offset is set to 1dp .

Next we add an observer,

documentViewModel.imagesLiveData.observe(this) {

and lastly, we add a listener to the RecyclerView to implement pagination.

mediaRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if ((gridLayoutManager.findLastVisibleItemPosition() == localDocumentsAdapter.itemCount - DOCUMENT_BUFFER) && dy > 0) {
view raw gistfile1.txt hosted with ❤ by GitHub
                          Pagination in RecyclerView

The value of DOCUMENT_BUFFER is 51, this is used to load new images as soon as we scroll by the 99th image (in a list of 150 images), in our list. You can set the value of this buffer to whatever you like, depending on your use case.

The dy > 0 is used to specify that the new images should be loaded only when the user is scrolling down.

This is a very simple demonstration of the usage of MediaStore API with pagination, to use the media documents efficiently in Android.



This is part of a multi-part series about learning to use Jetpack Compose through…

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.