Logo
Published on

Paging 3 Library with Jetpack Compose List and Coil Library in Android

Authors
  • Name
    Twitter

Photo by Fotis Fotopoulos on Unsplash

Jetpack Compose is new Android’s recommended way to build a native UI using power of Kotlin. It enable developers to create a native UI with less code and more flexibility in more intuitive and powerful way.
Advantages of using Jetpack Compose in android:-
1. Less Code
2. Intuitive
3. Accelerate Development
4. Powerful

The Paging 3 Library is a new library to fetch and display pages of data from a large data set from API or Database.

In this tutorial, we will discuss how to fetch YouTube data list from YouTube Data API using Paging 3 Library and show them in Jetpack Compose list with coil library for image rendering.

First of all we need to enable YouTube Data API access for our App in developer portal and need to get the YouTube data API key.

Now, Let’s start to build an app.

Add below line in Android manifest file :-

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Add below libraries in build.gradle file and sync:-

Retrofit and GSON

// retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")

// GSON
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

Coil for Image Loading

implementation("io.coil-kt:coil-compose:2.4.0")

Paging 3.0

implementation ("androidx.paging:paging-compose:3.3.0-alpha02")

Now, Let’s create the model class to map the response

data  class  YoutubeData(
var kind:String?,
var etag:String?,
var nextPageToken:String?,
var prevPageToken:String?,
var pageInfo:PageInfo,
var regionCode:String?,
var items:List<Items>,
)

data  class  PageInfo(
var totalResults:Int,
var resultsPerPage:Int
)
data  class  Items(
var kind:String?,
var etag:String?,
var id:ID,
var snippet:Snippet
)
data  class  ID(
var kind:String?,
var videoId:String?
)
data  class  Snippet(
var publishedAt:String?,
var channelId:String?,
var title:String?,
var description:String?,
var channelTitle:String?,
var liveBroadcastContent:String?,
var publishTime:String?,
var thumbnails:ThumbIcon
)
data  class  ThumbIcon(
var default:Default,
var medium:Medium,
var high:High
)
data  class  Default(
var url:String?,
var width:Int,
var height:Int
)
data  class  Medium(
var url:String?,
var width:Int,
var height:Int
)
data  class  High(
var url:String?,
var width:Int,
var height:Int
)

Now let’s create a interface for accessing the API. Let’s name it ApiService

interface  ApiService {
@GET("search")
suspend fun fetchApiData(
@Query("part") part: String,
@Query("channelId") channelId: String,
@Query("key") key: String,
@Query("order") order: String,
@Query("maxResult") maxResult:String,
@Query("type") type:String,
@Query("pageToken") pageToken:String
): YoutubeData

Now create the Object class for Retrofit client and set base url

object RetrofitClient {
private  fun getClient(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://www.googleapis.com/youtube/v3/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val apiService: ApiService = getClient().create(ApiService::class.java)

}

Now, create a Constant object to use the values in Paging Source class

object Constants {
val PART = "snippet"
val ORDER = "date"
val MAXRESULT = "50"
val VIDEO = "video"
val KEY = "youtube_data_api_key"
val CHANNEL_ID = "Youtube_Channel_id_for which_we need_to_fetch_the_data"
}

Now, create the PaginSource class to access the API method with paginantion

class  UserSource: PagingSource<String,Items>() {
override  fun getRefreshKey(state: PagingState<String, Items>): String {
return state.anchorPosition.toString()
}

override  suspend  fun load(params: LoadParams<String>): LoadResult<String, Items> {
return  try {
val nextPage = params.key ?: ""
val userList = RetrofitClient.apiService.fetchApiData(Constants.PART,Constants.CHANNEL_ID,Constants.KEY,Constants.ORDER,Constants.MAXRESULT,Constants.VIDEO,nextPage)
LoadResult.Page(
data = userList.items,
prevKey = null,
nextKey = userList.nextPageToken
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
} catch (exception: HttpException) {
return LoadResult.Error(exception)
}
}
}

Now, we need to create a Viewmodel class

class ItemViewModel : ViewModel() {
val user: Flow<PagingData<Items>> = Pager(PagingConfig(pageSize = 100)) {
UserSource()
}.flow.cachedIn(viewModelScope)
}

Now, we need to create a Composable function to access the Pagining response from the server

@Composable
fun UserList(modifier: Modifier = Modifier, viewModel: ItemViewModel, context: Context) {
UserInfoList(modifier, userList = viewModel.user, context)
}

Now we need to create a composable function to bind the response in Compose list and set Load status

@Composable
fun UserInfoList(modifier: Modifier, userList: Flow<PagingData<Items>>, context: Context) {
val userListItems: LazyPagingItems<Items> = userList.collectAsLazyPagingItems()

LazyColumn {
items(userListItems.itemCount){ item ->
ListViewItem(item = userListItems[item]!!)
}
userListItems.apply {
when {
loadState.refresh is LoadState.Loading -> {
//You can add modifier to manage load state when first time response page is loading
}

loadState.append is LoadState.Loading -> {
//You can add modifier to manage load state when next response page is loading
}

loadState.append is LoadState.Error -> {
//You can use modifier to show error message
}
}
}
}
}

In above code snippet, LazyColumn is way to create a vertical List in compose. If we want to create a horizontal list we need to use a LazyRow.

Now, we need to create a another composable method with name ListViewItem to create the list row and bind the data using their index

@Composable
fun ListViewItem(item: Items) {
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(12.dp),
verticalArrangement = Arrangement.Center
) {
AsyncImage(
model = ImageRequest
.Builder(context)
.data("${item.snippet.thumbnails.high.url}")
.crossfade(true)
.build(),
contentDescription = "${item.snippet.description}",
modifier = Modifier.size(480.dp, 360.dp),
contentScale = ContentScale.Crop,
error = painterResource(id = android.R.drawable.stat_notify_error),
placeholder = painterResource(id = android.R.drawable.presence_video_online)
)
Text(
text = "${item.snippet.title}",
modifier = Modifier
.fillMaxWidth(),
color = Color.Black,
fontWeight = FontWeight.Bold
)
Text(
text = "${item.snippet.description}",
modifier = Modifier.fillMaxWidth(),
color = Color.Blue,
fontWeight = FontWeight.Bold
)
Text(
text = "${item.snippet.channelTitle}",
modifier = Modifier.fillMaxWidth(),
color = Color.Green,
fontWeight = FontWeight.Bold
)
}
}

In above code snippet, We have created the column with following attribute

  1. Video preview Image :- We have used Coil library to fetch and show image from server
  2. Video Title
  3. Video Description
  4. Video Channel Title

And finally we need to add the below code in MainActivity

class  MainActivity : ComponentActivity() {
override  fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TestYputubeApiTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MyApp()
}
}
}
}
}

@Composable
fun MyApp() {
val itemViewModel = ItemViewModel()
val context = LocalContext.current
MaterialTheme {
UserList(viewModel = itemViewModel, context = context)
}
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview5() {
TestYputubeApiTheme {
MyApp()
}
}

Now we are ready to go. just run the app and you will see the list of all the videos uploaded by the given channel in descending order of date.

Happy Coding 👍 👍 👍