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),
)
}
|
 |
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.