How to Integrate REST API with Jetpack compose in Android?
Last updated Oct 14, 2021 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.