In this Jetpack compose tutorial we will learn how to integrate rest api 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 integrate rest api we are going to use Retrofit library which is a type safe HTTP client
In this example i am going to use this Api
{"booklist":[ {"id":"1", "title":"Practical Python Programming", "author":"Emenwa Global", "img":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/4907\/course_4907_image.jpg", "is_paid":"1","price":"699", "thumb_nail":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/4907\/course_4907_image.jpg", "source":"https:\/\/www.tutorialspoint.com\/ebook\/practical-python-programming\/index.asp", "download_path":"", "category_id":"1", "catebory_name": "IT Books"}, {"id":"2", "title":"Blueprints Visual Scripting for Unreal Engine Second Edition", "author":"Marcos Romero, Brenden Sewell", "img":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/936\/course_936_image.jpg", "is_paid":"1", "price":"599", "thumb_nail":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/936\/course_936_image.jpg", "source":"https:\/\/www.tutorialspoint.com\/author\/marcos_romero", "download_path":"", "category_id":"1", "catebory_name":"IT Books"}, {"id":"3","title":"Hands-on Data Virtualization with Polybase ", "author":"Pablo Echeverria", "img":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/1875\/course_1875_image.jpg", "is_paid":"1", "price":"720", "thumb_nail":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/1875\/course_1875_image.jpg", "source":"https:\/\/www.tutorialspoint.com\/ebook\/hands_on_data_virtualization_with_polybase\/index.asp", "download_path":"", "category_id":"1", "catebory_name":"IT Books"}, {"id":"4", "title":"Mastering Splunk 8", "author":"James D. Miller", "img":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/1561\/course_1561_image.jpg", "is_paid":"1", "price":"599", "thumb_nail":"https:\/\/d3mxt5v3yxgcsr.cloudfront.net\/courses\/1561\/course_1561_image.jpg", "source":"https:\/\/www.tutorialspoint.com\/ebook\/mastering_splunk_8\/index.asp", "download_path":"", "category_id":"1", "catebory_name":"IT Books"}, {"id":"7", "title":"Professional LAMP", "author":" Elizabeth Naramore, Jason Gerner, Matt Warden, Mo", "img":"https:\/\/allitbooks.net\/images\/8-professional-lamp.jpg", "is_paid":"0", "price":"0", "thumb_nail":"https:\/\/allitbooks.net\/images\/8-professional-lamp.jpg", "source":"https:\/\/allitbooks.net\/programming\/8-professional-lamp.html", "download_path":"https:\/\/allitbooks.net\/programming\/8-professional-lamp.html", "category_id":"1", "catebory_name":"IT Books"}, {"id":"8", "title":"Clean Code in JavaScript Develop Reliable ", "author":"Packt", "img":"https:\/\/freepdf-books.com\/doc-images\/40138.png", "is_paid":"0", "price":"0", "thumb_nail":"https:\/\/freepdf-books.com\/doc-images\/40138.png", "source":"https:\/\/freepdf-books.com\/clean-code-in-javascript-develop-reliable-maintainable-and-robust-javascri", "download_path":"https:\/\/freepdf-books.com\/clean-code-in-javascript-develop-reliable-maintainable-and-robust-javascri", "category_id":"1", "catebory_name":"IT Books"} ],"success":1} |
Let's add dependency for that
To integrate rest api we are using retrofit network library. 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.xml
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 Book which is used to parse the json data from server
data class Book ( val id: String, val title: String, val author: String, val img: String, @SerializedName("is_paid") val isPaid: String, val price: String, @SerializedName("thumb_nail") val thumbNail: String, val source: String, @SerializedName("download_path") val downloadPath: String, @SerializedName("category_id") val categoryId: String, @SerializedName("catebory_name") val categoryName: String ) |
create response object
data class GetBoolListResponse (
val booklist: List,
val success: Long
)
|
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.Call import retrofit2.http.GET import retrofit2.http.Headers interface Api { @Headers("Accept: Application/json") @GET("assets/test.json") fun fetchBookList() :Call? } |
Create Rest Client
Let's create retorfit object
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://www.rrtutors.com/" 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) } } |
Create a data class which will store books as per category
data class CategoryWithBooks(val id:String, val name:String, val bookList:List) |
ViewModel Object
Create BookViewModel which will have all the logic of fetching data through Rest Api
package com.example.jetpack.viewmodels import android.app.Application import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.example.jetpack.datamodels.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import retrofit2.awaitResponse class BookViewModel(appObj: Application) : AndroidViewModel(appObj) { private val _bookList = MutableStateFlow(Result(state = State.Loading, categoryWithBooks = listOf())) val bookList: StateFlow get() = _bookList init { fetchBooks() } private fun fetchBooks() { viewModelScope.launch { try { val call = RestClient().getService().fetchBookList() val response = call?.awaitResponse() if (response?.isSuccessful == true) { val getResponse = response.body() if (getResponse?.success == 1L) { val list = mutableListOf() val categories = mutableMapOf() getResponse.booklist.forEach { categories[it.categoryId] = it.categoryName } categories.forEach { map: Map.Entry -> val books = getResponse.booklist.filter { it.categoryId == map.key } .toMutableList() books.sortBy { it.title } list.add( CategoryWithBooks( map.key, map.value, books ) ) } list.sortBy { it.name } _bookList.emit( Result( state = State.Success, categoryWithBooks = list ) ) } else { _bookList.emit(Result(state = State.Failed, categoryWithBooks = listOf())) } } else { _bookList.emit(Result(state = State.Failed, categoryWithBooks = listOf())) } } catch (e: Exception) { Log.e("BookViewModel", e.message ?: "", e) _bookList.emit(Result(state = State.Failed, categoryWithBooks = listOf())) } } } } data class Result(val state: State, val categoryWithBooks: List) enum class State { Success, Failed, Loading } |
Add UI for book list item
@Composable fun BookListItem(book: Book) { 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(book.thumbNail) .into(object : CustomTarget() { override fun onLoadCleared(placeholder: Drawable?) {} override fun onResourceReady( resource: Bitmap, transition: com.bumptech.glide.request.transition.Transition? ) { bitmap.value = resource } }) val value = bitmap.value if (value != null) Image( value.asImageBitmap(), contentDescription = "image", Modifier .width(80.dp) .height(100.dp) ) 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 = book.title, fontSize = 20.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Bold ) Text( text = "Price: $${book.price}", fontSize = 16.sp, maxLines = 1, color = Color.LightGray, overflow = TextOverflow.Ellipsis ) Text( text = "Author: ${book.author}", color = Color.Gray, maxLines = 1, overflow = TextOverflow.Ellipsis ) }) }) }, modifier = Modifier .fillMaxWidth() .padding(top = 16.dp, start = 16.dp, end = 16.dp), ) } |
Create category header
@Composable fun CategoryHeader(name: String) { Row( content = { Text( text = name.uppercase(), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp ) }, modifier = Modifier .fillMaxWidth() .background(Color.DarkGray) .padding( top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp ) ) } |
Show list of book
@OptIn(ExperimentalFoundationApi::class) @Composable fun IntegrateRestApi(bookViewModel: BookViewModel) { val books = bookViewModel.bookList.collectAsState() JetPackTheme(darkTheme = true) { Scaffold( content = { when (books.value.state) { State.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator( progress = 0.8f, color = Color.Magenta, strokeWidth = 4.dp, ) } } State.Failed -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Failed to load list") } } State.Success -> { LazyColumn( contentPadding = PaddingValues( bottom = 16.dp ) ) { books.value.categoryWithBooks.forEach { stickyHeader { CategoryHeader(it.name) } itemsIndexed(items = it.bookList, itemContent = { pos, book -> BookListItem(book) }) } } } } }, modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( title = { Text("Books") } ) }, ) } } |
Full Code
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.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.asImageBitmap 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 com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.example.jetpack.datamodels.Book import com.example.jetpack.ui.theme.JetPackTheme import com.example.jetpack.viewmodels.BookViewModel import com.example.jetpack.viewmodels.State class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) val bookViewModel = ViewModelProvider(this).get(BookViewModel::class.java) setContent { IntegrateRestApi(bookViewModel) } } } @OptIn(ExperimentalFoundationApi::class) @Composable fun IntegrateRestApi(bookViewModel: BookViewModel) { val books = bookViewModel.bookList.collectAsState() JetPackTheme(darkTheme = true) { Scaffold( content = { when (books.value.state) { State.Loading -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator( progress = 0.8f, color = Color.Magenta, strokeWidth = 4.dp, ) } } State.Failed -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Failed to load list") } } State.Success -> { LazyColumn( contentPadding = PaddingValues( bottom = 16.dp ) ) { books.value.categoryWithBooks.forEach { stickyHeader { CategoryHeader(it.name) } itemsIndexed(items = it.bookList, itemContent = { pos, book -> BookListItem(book) }) } } } } }, modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( title = { Text("Books") } ) }, ) } } @Composable fun CategoryHeader(name: String) { Row( content = { Text( text = name.uppercase(), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp ) }, modifier = Modifier .fillMaxWidth() .background(Color.DarkGray) .padding( top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp ) ) } @Composable fun BookListItem(book: Book) { 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(book.thumbNail) .into(object : CustomTarget() { override fun onLoadCleared(placeholder: Drawable?) {} override fun onResourceReady( resource: Bitmap, transition: com.bumptech.glide.request.transition.Transition? ) { bitmap.value = resource } }) val value = bitmap.value if (value != null) Image( value.asImageBitmap(), contentDescription = "image", Modifier .width(80.dp) .height(100.dp) ) 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 = book.title, fontSize = 20.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Bold ) Text( text = "Price: $${book.price}", fontSize = 16.sp, maxLines = 1, color = Color.LightGray, overflow = TextOverflow.Ellipsis ) Text( text = "Author: ${book.author}", color = Color.Gray, maxLines = 1, overflow = TextOverflow.Ellipsis ) }) }) }, modifier = Modifier .fillMaxWidth() .padding(top = 16.dp, start = 16.dp, end = 16.dp), ) } |
Images
Conclusion: In this Jetpack compose tutorial series we have covered rest api integration using retrofit network library.
Article Contributed By :
|
|
|
|
8822 Views |