Basic Android MVVM with Kotlin Coroutines and Retrofit

Teng Tonghann
3 min readDec 21, 2020

When developing an Android application it is important to plan the architecture of the project. This will allow us to create good quality code and easy to maintain applications.

This article will focus on how to use Retrofit with Kotlin Coroutines to call asynchronous REST Api with MVVM architecture.

The project
We will be using the Dog CEO API 🐶 to get random puppy images, and we will be using an image loading library called Glide to display the image. Simple!

Set up a new project with Kotlin and other dependencies required:

def lifecycle_version = "2.2.0"

// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
// Loading images - Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
  • ViewModel and LiveData: Lifecycle-aware components perform actions in response to a change in the lifecycle status of another component, such as activities and fragments. These components help you produce better-organized, and often lighter-weight code, that is easier to maintain.
  • Retrofit: A type-safe HTTP client for Android.
  • Coroutines: for asynchronous programming
  • Glide: supports fetching, decoding, and displaying video stills, images, and animated GIFs.

Setting up a Model class
We will create a model class called ApiResponse which will be a representation of this response JSON.

data class ApiResponse (
var message: String,
var status: String
)

Setting up the Network Layer
Since we are using Retrofit for Network calls, let’s create a class that provides us the instance of the Retrofit Service class.

  • Creating the Retrofit instance
    To send out network requests to an API, we need to use the ServiceBuilder class.
object ServiceBuilder {

private const val API_URL = "https://dog.ceo/api/breeds/"

private fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val apiService: ApiService by lazy {
provideRetrofit().create(ApiService::class.java)
}

}
  • Define the Endpoints
    With Retrofit, endpoints are defined inside of an interface using special Retrofit annotations to endcode details about the parameter and request method. The ApiService interface will look as follows.
interface ApiService {

@GET("image/random")
suspend fun getRandomDog(): Response<ApiResponse>
}

The suspend functions can only be called from Coroutines. Suspend functions are a core element of Coroutines. They help the Coroutines to suspend (paused) or resumed during execution.

  • Android Permission
    let’s add permission in the AndroidManifest file.
<uses-permission android:name="android.permission.INTERNET"/>

Setting up the ViewModel
Let’s create our ViewModel class called MainViewModel

class MainViewModel : ViewModel(), CoroutineScope by MainScope() {

companion object {
const val LOG = "GetDogLog"
}

private val _message = MutableLiveData<String>()
val message: LiveData<String>
get() = _message

fun getRandomDog() {
launch(Dispatchers.Main) {
try {
val response = ServiceBuilder.apiService.getRandomDog()
if (response.isSuccessful && response.body() != null) {
response.body()?.message?.let {
_message.value = it
Log.d(LOG, it)
}
} else {
Log.d(LOG, "Server error")
}
} catch (exception: Exception) {
exception.message?.let {
Log.d(LOG, it)
}
}
}
}
}
  • In order to call a Coroutines, we have to implement a CoroutineScope . This provides the scope in which the given Coroutines should send out its result. In this case, we will implement the MainScope() , which creates the scope for UI components.
  • val messageis observable (LiveData), which means that an observer is notified when the data held by the LiveData object changes.

Setting the View
Our UI is pretty simple. Here is activity_main.xml :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageView
android:id="@+id/imageViewDog"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerInParent="true"
android:src="@color/design_default_color_secondary" />

<Button
android:id="@+id/randomDog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="Random Dog" />

</RelativeLayout>

Our MainActivity class:

class MainActivity : AppCompatActivity() {

lateinit var viewModel: MainViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

viewModel.getRandomDog()

randomDog.setOnClickListener {
viewModel.getRandomDog()
}

viewModel.message.observe(
this,
{
Glide.with(this)
.load(it)
.into(imageViewDog)
}
)
}
}

That’s all, our project is set up. Now, let’s run our project. You will be able to see a random puppy 🐶 every time you click the Random Dog button.

That is it for this article. Thank you so much for your time!

--

--