- Published on
Paging 3 Library with Jetpack Compose List and Coil Library in Android
- Authors
- Name
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. PowerfulThe 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
- Video preview Image :- We have used Coil library to fetch and show image from server
- Video Title
- Video Description
- 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 👍 👍 👍