Join Login Create a Request

Jetpack Compose REST API calls

|
Jetpack Compose REST API calls

Jetpack compose made android UI development easy for a lot of developers. I personally felt liberated from the chains of XML based designs the day i started learning jetpack. Before jetpack compose i couldn't help but use react native (Expo) as my go-to mobile development technology. Rest in peace my XML designs. However, like any other newbie on the corner, i didn't know how to implement API calls, having an external API server that returns a list of objects and using that list to render my LazyColumn felt so hard and far off a feature in my head. Well, not now at least. This piece can help you set up your models, view models, and repository classes that can help you fetch data from APIs.

Requirements

  • I'm going to assume you already have a project created and a jetpack screen waiting for live data.
  • An API that will return the JSON data
  • Gson - An open-source java library used to serialize and deserialize Java objects to JSON
  • Volley - An HTTP library for making API requests to our server.

Add android dependencies

Open your app build.gradle and add the following dependencies

//app/build.gradle
//----------------------------

plugins {
    //---------------------
    id 'dagger.hilt.android.plugin'
}

dependencies {
    implementation "com.android.volley:volley:1.2.1"
    implementation "com.google.code.gson:gson:2.8.9"
    implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
    implementation "com.google.dagger:hilt-android:2.38.1"
}
//----------------------------

Create data classes (models)

Inside your project, let's create the model class. For the sake of illustration, let's make a model for students. Our API will return a list of students. First, create a package called models and another package inside called students. Then create a students model class and a student class.

Remember: Your models have to match with the data you'll receive from your API.

mkdir app/java/com.example/models/students
touch Students.kt
touch Student.kt
// Students.kt
package com.example.models.students

data class Students(
    val count: Int,
    val next: String,
    val previous: String,
    val results: ArrayList<Student>,
)
// Student.kt
package com.example.models.students

data class Student(
    val name: String,
    val surname: String,
    val age: String,
    val level: String,
    val id: Int
)

Repository class

Now let's create a class that'll manage the fetching of data from our API and or locally stored data. This is where we'll use volley and gson. It's possible to mix the functions of this class with your view models but that will make everything messy, so having it in its own separate file can help make everything connect in a logical way.

Resource class

mkdir app/java/com.example/utils
touch Resource.kt
// Resource class
package com.example.utils

sealed class Resource<T>(val data: T? = null, val message: String? = null) {
    class Success<T>(data: T): Resource<T>(data)
    class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
}

Data Repository

mkdir app/java/com.example/repository/students
touch students.kt
// Resource class
package com.example.repository.students

import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import com.android.volley.Request
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import dagger.hilt.android.scopes.ActivityScoped
import com.example.models.students.Students
import com.example.Resource

@ActivityScoped
class StudentsRepository {
    fun getStudentsResponse(context: Context): MutableLiveData<Resource<Students>> {
        val _setStudentsData: MutableLiveData<Resource<Students>> = MutableLiveData<Resource<Students>>()
        // Your API endpoint
        val url = "https://api.example.com/students"
        val gson = Gson()
        try {
            val queue = Volley.newRequestQueue(context)
            Log.d("TAG", "getStudentsReponse: $queue")
            val stringRequest = StringRequest(
                Request.Method.GET, url,
                { response ->
                    val responseVales: Students = gson.fromJson(
                        response,
                        object : TypeToken<Students>() {}.type
                    )
                    _setStudentsData.postValue(Resource.Success(responseVales))
                }
            ) { error ->
                _setStudentsData.postValue(Resource.Error("An unknown error occured: ${error.localizedMessage}"))
            }
            queue.add(stringRequest)
        } catch (e: Exception) {
            _setStudentsData.postValue(Resource.Error("An unknown error occured."))
        }

        return _setStudentsData
    }
}

View Models

ViewModel is a class used to manage the data of an Activity or a Fragment.

mkdir app/java/com.example/videmodels/students
touch StudentsViewModel.kt
touch StudentsViewModelFactory.kt

StudentsViewModel.kt

// StudentsViewModel.kt
package com.example.viewmodels.students

import android.content.Context
import android.util.Log
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.repository.students.StudentsRepository
import com.example.models.students.Students
import com.example.utils.Resource
import javax.inject.Inject


class studentsViewModel @Inject constructor(
    private val studentsRepository: StudentsRepository
): ViewModel(){
    var studentsLoading = mutableStateOf(false)
    private var _getStudentsData: MutableLiveData<Students?> = MutableLiveData<Students?>()
    var getStudentsData: LiveData<Students?> = _getStudentsData

    fun getStudentsData(context: Context): Resource<Students> {
        val result = studentsRepository.getStudentsResponse(context = context)

        MediatorLiveData<Resource<Students?>>().apply {
            addSource(result) {
                jobsLoading.value = true
                _getStudentsData.value =  if (it.data !== null) it.data else null
            }
            observeForever {
                Log.d("TAG", "Get Students")
            }
        }
        return Resource.Error("Please Wait")
    }
}

StudentsViewModelFactory.kt

ViewModelFactory is a class to instantiate and return ViewModel. In our case instantiate and return StudentsViewModel.

package com.example.viewmodels.students

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.repository.students.StudentsRepository

class StudentsViewModelFactory(private val studentsRepository: StudentsRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(StudentsViewModel::class.java)) {
            return StudentsViewModel(studentsRepository) as T
        }
        throw IllegalArgumentException("Unknown viewmodel class")
    }
}

Now we're done with this nonsense of separating concerns. All our helper classes are set and ready to fetch data into our jetpack compose UIs. Let's create a com.example.ui/screens/Students composable function. You'll then have to include the class in your MainActivty class or whichever other Main class you have.

mkdir app/java/com.example/ui/screens
touch Students.kt
package com.example.ui.screens

import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SignalCellularConnectedNoInternet0Bar
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.data.students.StudentsRepository
import com.example.utils.Resource
import com.example.viewmodels.students.StudentsViewModel
import com.example.viewmodels.students.StudentsViewModelFactory

@Composable
fun StudentsScreen(){
    val viewModel: StudentsViewModel = viewModel(
        factory = StudentsViewModelFactory(studentsViewModelFactory = StudentsViewModelFactory())
    )
    val context = LocalContext.current
    val scaffoldState = rememberScaffoldState()
    val scrollState = rememberLazyListState()
    val getStudentsData = viewModel.getStudentsData.observeAsState()

    LaunchedEffect(Unit) {
        val result = viewModel.getStudentsData(context)

        if (result is Resource.Success) {
            Toast.makeText(context, "Fetching data Success", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(context, result.message, Toast.LENGTH_SHORT).show()
        }
    }

    Surface(
        modifier = Modifier.fillMaxSize()
    ) {
        Scaffold(
            modifier = Modifier.fillMaxSize(),
            scaffoldState = scaffoldState
        ) {

             Column(
                 modifier = Modifier
                     .fillMaxSize()
                     .background(Color.LightGray.copy(alpha = 0.2f))
             ) {

                 if (!viewModel.studentsLoading.value) {
                     Column(
                         modifier = Modifier.fillMaxSize(),
                         verticalArrangement = Arrangement.Center,
                         horizontalAlignment = Alignment.CenterHorizontally
                     ) {
                         LinearProgressIndicator()
                     }
                 }


                 if(viewModel.studentsLoading.value){
                     if(getStudentsData.value !== null){
                         if(getStudentsData.value !== null && getStudentsData.value!!.count > 0) {
                             LazyColumn(
                                 state=scrollState
                             ){
                              if(getStudentsData.value !== null){
                                  items(getStudentsData.value!!.results.size) { index ->
                                      var article = getStudentsData.value!!.results[index]
                                      Text(text=student.name)
                                  }
                              }
                             }
                         }
                     }else{
                     // Handle network error
                 }
             }
          }
       }
    }
}

That's all, happy coding


Tags.




Recent Posts

Ways to differentiate your services from others as a freelancer
Ways to differentiate your services from others as a freelancer
Ways to differentiate your services from others as a freelan..
Read More
Starting a referral or affiliate program as a freelancer.
Starting a referral or affiliate program as a freelancer.
How to start a referral program as a freelancer. These are s..
Read More
How to build referrals as a freelancer
How to build referrals as a freelancer
Ways to build your referrals as a freelancer. Expand your lo..
Read More
Handling customer Service as a Freelancer
Handling customer Service as a Freelancer
How to handle customer services as a freelancer. Learn the b..
Read More