How to implement pagination in Jetpack compose?

Last updated Nov 10, 2021

In this Jetpack compose tutorial we will learn how to implement pagination using Jetpack compose in Android application.

Let's get started

Step 1: Create android application in android studio

Step 2: Follow step for setup Jetpack Compose with Android Studio

To handle big amount of data in app we use Paging Library.

Lets add dependency for paging

implementation 'androidx.paging:paging-compose:1.0.0-alpha14'

 

 

Api we are using is given below:

https://reqres.in/api/users?page=1&per_page=6

 

 

Response of Api is aslo given below:

{"page":1,

 "per_page":6,

 "total":12,

 "total_pages":2,

 "data":[

 {

 "id":1,

 "email":"george.bluth@reqres.in",

 "first_name":"George",

 "last_name":"Bluth",

 "avatar":"https://reqres.in/img/faces/1-image.jpg"

 },

 {

 "id":2,

 "email":"janet.weaver@reqres.in",

 "first_name":"Janet",

 "last_name":"Weaver",

 "avatar":"https://reqres.in/img/faces/2-image.jpg"

 },

 {

 "id":3,

 "email":"emma.wong@reqres.in",

 "first_name":"Emma",

 "last_name":"Wong",

 "avatar":"https://reqres.in/img/faces/3-image.jpg"

 },

 {

 "id":4,

 "email":"eve.holt@reqres.in",

 "first_name":"Eve",

 "last_name":"Holt",

 "avatar":"https://reqres.in/img/faces/4-image.jpg"

 }, 

 {

 "id":5,

 "email":"charles.morris@reqres.in",

 "first_name":"Charles",

 "last_name":"Morris",

 "avatar":"https://reqres.in/img/faces/5-image.jpg"

 },

 {

 "id":6,

 "email":"tracey.ramos@reqres.in",

 "first_name":"Tracey",

 "last_name":"Ramos",

 "avatar":"https://reqres.in/img/faces/6-image.jpg"

}],

 "support":{

 "url":"https://reqres.in/#support-heading",

 "text":"To keep ReqRes free, contributions towards server costs are appreciated!"

 }

}

 

 

we are going to use Retrofit network library which is a type safe HTTP client, add requred dependencies to gradle file

api 'com.squareup.retrofit2:retrofit:2.9.0'
api 'com.squareup.retrofit2:converter-gson:2.9.0'

 

 

Add Internet Permission in AndroidManifest.xm

 

Add Glide dependency

To work with images we are using the glide image cache library

implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

 

 

Create a data class for UserInfo which is used to parse the json data from server

import com.google.gson.annotations.SerializedName

data class UserInfo(
    val id: String,
    val email: String,
    @SerializedName("first_name")
    val firstName: String,
    @SerializedName("last_name")
    val lastName: String,
    val avatar: String
)

 

 

Create Response Object

data class GetUsersResponse(
    val page: Int,
    val data: List,
    val support: Support,
    @SerializedName("total_pages")
    val totalPages: Int,
    @SerializedName("per_page")
    val perPage: Int,
)

data class Support(
    val url: String,
    val text: String
)

 

 

To work with retrofit we need to create interface which will comunicate server by respected apis.

Create interface for api

package com.example.jetpack.datamodels

import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query


interface Api {
    @Headers("Accept: Application/json")
    @GET("users")
    suspend fun getUserList(@Query("page") page: Int): GetUsersResponse
}

 

 

Create Rest Client

package com.example.jetpack.datamodels

import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit


class RestClient{
    private val service: Api
    fun getService(): Api {
        return service
    }

    init {
        val baseUrl = "https://reqres.in/api/"
        val client = OkHttpClient.Builder()
        client.connectTimeout(2, TimeUnit.MINUTES)
        client.readTimeout(2, TimeUnit.MINUTES)
        client.writeTimeout(2, TimeUnit.MINUTES)
        val gson = GsonBuilder()
            .serializeNulls()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .setLenient()
            .create()
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(client.build())
            .build()
        service = retrofit.create(Api::class.java)
    }
}

 

 

We need a data source that will handle pagination. Lets create that

package com.example.jetpack.datamodels

import androidx.paging.PagingSource
import androidx.paging.PagingState
import retrofit2.HttpException
import java.io.IOException


class UserPagingSource : PagingSource() {

    override fun getRefreshKey(state: PagingState): Int? {
        return state.anchorPosition
    }

    override suspend fun load(params: LoadParams): LoadResult {

        return try {
            val page = params.key ?: 1
            val userListResponse = RestClient().getService().getUserList(page)
            LoadResult.Page(
                data = userListResponse.data,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (userListResponse.data.isEmpty()) null else page + 1
            )
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            return LoadResult.Error(exception)
        }
    }
}

 

 

Let's configure pager in view model

class UserInfoViewModel(appObj: Application) : AndroidViewModel(appObj) {
    val users: Flow> = Pager(PagingConfig(pageSize = 6)) {
        UserPagingSource()
    }.flow.cachedIn(viewModelScope)
}

 

 

lets create ui for userinfo

@Composable
fun UserInfoView(userInfo: UserInfo?) {
    Card(
        elevation = 4.dp,
        content = {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 16.dp, vertical = 16.dp), content = {
                    val bitmap = remember { mutableStateOf(null) }
                    val context = LocalContext.current
                    Glide.with(context)
                        .asBitmap()
                        .load(userInfo?.avatar ?: "")
                        .into(object : CustomTarget() {
                            override fun onLoadCleared(placeholder: Drawable?) {}
                            override fun onResourceReady(
                                resource: Bitmap,
                                transition: Transition?
                            ) {
                                bitmap.value = resource
                            }
                        })
                    val value = bitmap.value
                    if (value != null)
                        Image(
                            value.asImageBitmap(),
                            modifier = Modifier
                                .clip(RoundedCornerShape(10.dp)).width(80.dp).height(80.dp),
                            contentScale = ContentScale.Crop,
                            contentDescription = "image"
                        )
                    else
                        Box(
                            content = {
                                Text(
                                    text = "Loading",
                                    fontSize = 16.sp,
                                )
                            }, modifier = Modifier
                                .width(80.dp)
                                .height(100.dp)
                                .border(
                                    width = 1.2.dp,
                                    color = Color.White,
                                    shape = RectangleShape
                                ),
                            contentAlignment = Alignment.Center
                        )

                    Spacer(modifier = Modifier.size(16.dp))
                    Column(
                        modifier = Modifier.weight(2F),
                        content = {
                            Text(
                                text = "${userInfo?.firstName} ${userInfo?.lastName}",
                                fontSize = 20.sp,
                                maxLines = 2,
                                overflow = TextOverflow.Ellipsis,
                                fontWeight = FontWeight.Bold
                            )
                            Text(
                                text = "${userInfo?.email}",
                                color = Color.Gray,
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis
                            )
                        })
                })
        },
        modifier = Modifier
            .fillMaxWidth()
            .padding(top = 16.dp, start = 16.dp, end = 16.dp),
    )
}

 

Jetpack compose Pagination example

 

Complete example code for Jetpack compose Pagination

package com.example.jetpack

import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModelProvider
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.example.jetpack.datamodels.UserInfo
import com.example.jetpack.ui.theme.JetPackTheme
import com.example.jetpack.viewmodels.UserInfoViewModel
import com.google.accompanist.pager.*

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
        val userInfoViewModel = ViewModelProvider(this).get(UserInfoViewModel::class.java)
        setContent {
            PaginationDemo(userInfoViewModel)
        }
    }
}

@Composable
fun PaginationDemo(userInfoViewModel: UserInfoViewModel) {
    val users: LazyPagingItems = userInfoViewModel.users.collectAsLazyPagingItems()
    JetPackTheme(darkTheme = true) {
        Scaffold(
            content = {
                LazyColumn {
                    items(users) { item ->
                        UserInfoView(item)
                    }
                }
            },
            modifier = Modifier.fillMaxSize(),
            topBar = {
                TopAppBar(
                    title = { Text("Users") }
                )
            },
        )
    }
}

@Composable
fun UserInfoView(userInfo: UserInfo?) {
    Card(
        elevation = 4.dp,
        content = {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 16.dp, vertical = 16.dp), content = {
                    val bitmap = remember { mutableStateOf(null) }
                    val context = LocalContext.current
                    Glide.with(context)
                        .asBitmap()
                        .load(userInfo?.avatar ?: "")
                        .into(object : CustomTarget() {
                            override fun onLoadCleared(placeholder: Drawable?) {}
                            override fun onResourceReady(
                                resource: Bitmap,
                                transition: Transition?
                            ) {
                                bitmap.value = resource
                            }
                        })
                    val value = bitmap.value
                    if (value != null)
                        Image(
                            value.asImageBitmap(),
                            modifier = Modifier
                                .clip(RoundedCornerShape(10.dp)).width(80.dp).height(80.dp),
                            contentScale = ContentScale.Crop,
                            contentDescription = "image"
                        )
                    else
                        Box(
                            content = {
                                Text(
                                    text = "Loading",
                                    fontSize = 16.sp,
                                )
                            }, modifier = Modifier
                                .width(80.dp)
                                .height(100.dp)
                                .border(
                                    width = 1.2.dp,
                                    color = Color.White,
                                    shape = RectangleShape
                                ),
                            contentAlignment = Alignment.Center
                        )

                    Spacer(modifier = Modifier.size(16.dp))
                    Column(
                        modifier = Modifier.weight(2F),
                        content = {
                            Text(
                                text = "${userInfo?.firstName} ${userInfo?.lastName}",
                                fontSize = 20.sp,
                                maxLines = 2,
                                overflow = TextOverflow.Ellipsis,
                                fontWeight = FontWeight.Bold
                            )
                            Text(
                                text = "${userInfo?.email}",
                                color = Color.Gray,
                                maxLines = 1,
                                overflow = TextOverflow.Ellipsis
                            )
                        })
                })
        },
        modifier = Modifier
            .fillMaxWidth()
            .padding(top = 16.dp, start = 16.dp, end = 16.dp),
    )
}

@ExperimentalPagerApi
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {

}

 

Conclusion: In this Jetpack compose tutorial we covered fetch data from rest api with pagination implemention using Pager compose function.

 

Article Contributed By :
https://www.rrtutors.com/site_assets/profile/assets/img/avataaars.svg

2523 Views