Introduction
What is MVVM and Why use MVVM?
What is Retrofit?
MVVM and Retrofit in Action
Introduction
Building and manufacturing an app is like building and manufacturing a Car. Just like when you build a car, you need to use different parts and tools to make it strong and fun to drive. In the world of apps, developers use special patterns and libraries to make their apps strong and work well. There are many patterns or architectures to build apps but here I will be focusing on MVVM.๐
What is MVVM and Why use MVVM?
MVVM stands for Model-View-ViewModel (MVVM). In this architecture pattern
i] The Model handles data storage and retrieval.
ii] The View manages user interactions and displays information.
iii] The ViewModel serves as a mediator between the Model and View.
MVVM is used because the ViewModel components can be reused with different Views. MVVM also makes it easier to fix problems or make improvements in the app. We can test each part separately to make sure they work correctly.
So, by using MVVM in Android development, we make the app organized, easier to work with, and more reliable.๐
What is Retrofit?
Retrofit is like a messenger that helps your app talk to the internet. Just like we humans talk to other humans using phones or computers. Similarly, your app needs to communicate with other computers to get information. So retrofit makes it easier to send requests and receive responses from those computers to your app.
You can think of retrofit as a translator which can convert the information sent from your app into a language that other computers can understand and then convert the response back into the language your app can understand and work with it.
For more information on retrofit, you can refer to Retrofit.
MVVM and Retrofit in Action
First, add the Internet Permission to the manifest file. This ensures that data retrieval issues are avoided when the app is built and launched. Or else, you know what happens.๐ซ If you're new to this, give it a try!๐คง
Add the following <uses-permission android:name="android.permission.INTERNET"/>
permission in the manifest file as shown below.
Then let's add the required dependency for your app in the build.Gradle(module:app) file. The dependencies are provided below but make sure when you use it, you use the latest version of the dependencies.
//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
// LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
//couriteins
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
Now create four packages named Api, Models, Repository, and ViewModel. In
app-> java-> com.example.quotes as shown below.
For this project, we will be using the following Quotes API: https://type.fit/api/quotes. We will focus on GET requests for this blog post. For Post, Patch, Delete, and Put requests, I will write a separate blog post.
In the Models package, let's create a data class using the Kotlin-to-JSON plugin. To create the data class, copy the API response and paste it as shown below. Name it as QuotesList and Click Generate.
Moving on to the next step. Right-click on package Api -> add one Interface named ApiInterface or the name you prefer. And an object class named ApiUtilities as shown below.
Now in the ApiInterface class, write the below-provided code.
import com.example.quotes.Models.QuotesList
import com.example.quotes.Models.QuotesListItem
import retrofit2.Response
import retrofit2.http.GET
interface ApiInterface{
@GET("quotes")
suspend fun getQuotes():Response<QuotesList>
}
The explanation for the code is given below :)
1] interface ApiInterface
: We define an interface named ApiInterface to encapsulate our API methods.
2] @GET("quotes")
: This annotation signifies that we are making a GET request to the "quotes" endpoint. Adjust the endpoint URL according to the API you're integrating.
3] suspend fun getQuotes()
Represent an API call to retrieve quotes. The suspend
modifier means that this modifier can be called from the coroutine, which can be paused and resumed later without blocking the thread.
4] The Response<QuotesList>
Return type means that we can expect the API response to contain a list of quotes, where each quote will be represented as an instance of the QuotesList model class. The attributes of the QuotesList model class might include author, content, and year.
Add the following code to the ApiUtilities class in the Object file. This code contains a function that returns an instance of the Retrofit Object.
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ApiUtilites {
val BASE_URL = "https://type.fit/api/"//defining baseurl
//the below function will return the retrofit instance
fun getRetrofitInstance(): Retrofit {
return Retrofit
.Builder()//This creates an instance of Retrofit.Builder class
.baseUrl(BASE_URL)//This sets the base URL for all the API endpoints
.addConverterFactory(GsonConverterFactory.create())//this convert the response data into JSON using the Gson library.
.build()//This builds the Retrofit instance with the configured settings and returns the final instance.
}
}
Now let's move on to the Repository package and create a class named QuotesRepository which takes the ApiInterface object as a parameter as shown below followed by the explanation of the code.
The class contains a MutableLiveData
object named mutableLiveData
, which is of type QuotesList
. MutableLiveData
is a subclass of LiveData
which allows us to set values. The quotesLiveData
property is an immutable LiveData
object that provides read-only access to mutableLiveData
from other classes.
The getquotesfromQuotesRepository
function is a suspend function that is used to fetch quotes data from the API. It calls the getQuotes
method from the apiInterface
object, which returns a response object (result
). If the response body is not null, it sets the value of mutableLiveData
using the postValue
method.
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.quotes.Api.ApiInterface
import com.example.quotes.Models.QuotesList
class QuotesRepository(private val apiInterface: ApiInterface) {
private val mutableLiveData = MutableLiveData<QuotesList>()
val quotesliveData: LiveData<QuotesList>
get() = mutableLiveData
suspend fun getquotesfromQuotesRepository() {
val result = apiInterface.getQuotes()
if (result.body() != null) {
mutableLiveData.postValue(result.body())
}
}
}
Here, note how data is flowing and going to views to update the user interface(UI). there is a block diagram at the end of the blog.
The QuotesViewModel
class takes a QuotesRepository
object as a constructor parameter. When an instance of the QuotesViewModel
is created, and the init block is called and launches a coroutine in the IO context using viewModelScope.launch(
Dispatchers.IO
)
. Within this coroutine, the getquotesfromQuotesRepository()
function of the quotesRepository is called to fetch quote data from the API. This ensures that the API call is made in the background thread to avoid blocking the UI.
class QuotesViewModel(private val quotesRepository: QuotesRepository) : ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO) {
quotesRepository.getquotesfromQuotesRepository()
}
}
val quotes :LiveData<QuotesList>
get() = quotesRepository.quotesliveData
}
The quotes
property is a read-only LiveData
object that is exposed to the UI for observing the quotes data. It is derived from the quotesliveData
property of the quotesRepository
. This means that whenever the quotesliveData
property in the quotesRepository
is updated, the quotes property will automatically reflect the changes.
Now make another class in the same ViewModel Package named quotesViewModelFactory. We make this class because we are giving a quotesRepository object as a parameter to the QuotesViewModel constructor. Inside the create
method, a new instance of QuotesViewModel
is created, passing the quotesRepository
as a parameter. Since the create
method is generic, it casts the created QuotesViewModel
to type T
before returning it.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.quotes.Repository.QuotesRepository
class QuotesViewModelFactory(private val quotesRepository: QuotesRepository):ViewModelProvider.Factory{
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return QuotesViewModel(quotesRepository) as T
}
}
Next moving on to MainActivity.kt file, this class utilizes the QuotesViewModel
and QuotesViewModelFactory
to retrieve and observe a list of quotes as shown below.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.ViewModelProvider
import com.example.quotes.Api.ApiInterface
import com.example.quotes.Api.ApiUtilites
import com.example.quotes.Repository.QuotesRepository
import com.example.quotes.ViewModel.QuotesViewModel
import com.example.quotes.ViewModel.QuotesViewModelFactory
class MainActivity : AppCompatActivity() {
lateinit var quotesViewModel: QuotesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val apiInterface = ApiUtilites.getRetrofitInstance().create(ApiInterface::class.java)
val quotesRepository = QuotesRepository(apiInterface)
quotesViewModel = ViewModelProvider(this,QuotesViewModelFactory(quotesRepository)).get(QuotesViewModel::class.java)
quotesViewModel.quotes.observe(this,{
Log.d("DATA", "Authors: ${it.map { it.text }}")
})
}
}
First, define lateinit var quotesViewModel: QuotesViewModel
. The lateinit
keyword signifies that the property will be assigned a value later in the code, prior to being used. Next, create an instance of ApiInterface
using val apiInterface = ApiUtilites.getRetrofitInstance().create(ApiInterface::
class.java
)
, and pass this instance to the quotesRepository
object as indicated above.
The ViewModelProvider
is used to create an instance of QuotesViewModel
by passing the this
reference (referring to the MainActivity
instance) and a QuotesViewModelFactory
instance as parameters.
quotesViewModel.quotes.observe(this, { quotes -> ... })
sets up an observer on the quotes
LiveData property of quotesViewModel
.
When the value of the quotesLiveData
changes, the lambda function { quotes -> ... }
is executed.
Inside the lambda, the authors' texts of the quotes are logged using Log.d("DATA", "Authors: ${
quotes.map
{ it.text }}")
.
Voila, it's done. And in the below block diagram, you can see the flow of how each class is interacting.
If you find this article helpful, have a question or spot an error/typo, please leave a comment in the comment section below. And thanks to my friends Dr.GoodDay, Sai, and ChatGPT for helping me with this.๐ซ