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

Rest api integration with Jetpack compose

 

Conclusion: In this Jetpack compose tutorial series we have covered rest api integration using retrofit network library.

Article Contributed By :
https://www.rrtutors.com/site_assets/profile/assets/img/avataaars.svg

8400 Views