<p>This post is about the MVVM pattern in Android, and how to build a project with the MVVM pattern using Retrofit.</p> 
 
 
 
<div class="wp-block-buttons is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-a89b3969 wp-block-buttons-is-layout-flex"> 
<div class="wp-block-button"><a class="wp-block-button__link has-background" style="background-color: #240372;" href="https://github.com/arunk7839/MVVMWithNetworkSource"><strong>DOWNLOAD CODE</strong></a></div> 
</div> 
<h4> </h4> 
<p><amp-youtube layout="responsive" width="1200" height="675" data-videoid="rh2ZBEOHXVk" title="Android MVVM Pattern with Retrofit in Kotlin"><a placeholder href="https://youtu.be/rh2ZBEOHXVk"><img src="https://i.ytimg.com/vi/rh2ZBEOHXVk/hqdefault.jpg" layout="fill" object-fit="cover" alt="Android MVVM Pattern with Retrofit in Kotlin"></a></amp-youtube></p> 
<h4><span style="color: #000080;"><strong>What is MVVM?</strong></span></h4> 
<ul> 
<li>MVVM architecture is a <strong><span style="color: #008000;">Model-View-ViewModel</span> </strong>architecture.</li> 
<li>It allows separating the user interface logic from the business (or the back-end) logic.</li> 
<li>Its target is to keep UI code simple and free of app logic in order to make it easier to manage and test. </li> 
</ul> 
<p>MVVM has mainly the following layers:</p> 
<p><span style="color: #000080;"><strong>Model</strong></span>:</p> 
<p>The model represents the data and the business logic of the Application. It consists of the business logic &#8211; local and remote data source, model classes, repository.</p> 
<p><strong><span style="color: #000080;">View</span></strong>:</p> 
<p>The view consists of the UI Code(Activity, Fragment), XML. The view role in this pattern is to observe (or subscribe to) a ViewModel observable to get data in order to update UI elements accordingly.</p> 
<p><strong><span style="color: #000080;">ViewModel</span></strong>: </p> 
<p>ViewModel interacts with the model and also prepares observable(s) that can be observed by a View. <br />One of the important implementation strategies of this layer is to decouple it from the View, i.e, ViewModel should not be aware of the view who is interacting with.</p> 
<h4><span style="color: #000080;"><strong>Creating new project</strong></span></h4> 
<p>1 . Create a new project by going to <span style="color: #008000;"><strong>File ⇒ New Android Project</strong></span>, select <span style="color: #008000;"><strong>Empty</strong></span> Activity, provide <span style="color: #008000;"><strong>app</strong></span> name, select language to <span style="color: #008000;"><strong>kotlin</strong></span> and then finally click on <span style="color: #0000ff;"><strong>finish</strong></span>.</p> 
<p>2 . Open app-level build.gradle file and add the below changes:</p> 
<ul> 
<li>Add the below libraries under the <span style="color: #008000;"><strong>dependencies</strong></span> section<strong>:</strong></li> 
</ul> 
<pre>dependencies {<br /><strong><span style="color: #008000;"> //ViewModel and livedata</span></strong><br /> implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"<br /><br /><span style="color: #008000;"><strong> //Retrofit</strong></span><br /> implementation 'com.squareup.retrofit2:retrofit:2.9.0'<br /> implementation 'com.squareup.retrofit2:converter-gson:2.9.0'<br /><br /><strong><span style="color: #008000;"> //Glide</span></strong><br /> implementation 'com.github.bumptech.glide:glide:4.12.0'<br /> implementation 'com.github.bumptech.glide:compiler:4.12.0'<br />}</pre> 
<ul> 
<li>Inside the android block, add viewBinding block with property enabled to true.</li> 
</ul> 
<pre>viewBinding {<br /> enabled = true<br />}</pre> 
<ul> 
<li>Finally, click on <strong><span style="color: #0000ff;">Sync Now.</span></strong></li> 
</ul> 
<p>3. Open the AndroidManifest.xml file and add the internet permission above the application element.</p> 
<pre><;uses-permission android:name="android.permission.INTERNET"/>;</pre> 
<h4><span style="color: #000080;"><strong>Setup the Data Layer</strong></span></h4> 
<p>In the data layer, we have to prepare the model for the data, and an API call needs to be implemented. </p> 
<h5><span style="color: #0000ff;"><strong>Creating Model class</strong></span></h5> 
<p>I will use<span style="color: #008000;"> <strong>&#8220;https://fake-movie-database-api.herokuapp.com/api?s=batman&#8221;</strong></span> API to fetch the data.</p> 
<p>The API JSON response will be like this.</p> 
<pre>{<br />"Search": [<br />{<br />"imdbID": "tt0096895",<br />"Title": "Batman",<br />"Year": "1989",<br />"Poster": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTYwNjAyODIyMF5BMl5BanBnXkFtZTYwNDMwMDk2._V1_.jpg"<br />},<br />{<br />"imdbID": "tt0468569",<br />"Title": "The Dark Knight",<br />"Year": "2008",<br />"Poster": "https://ia.media-imdb.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_UX182_CR0,0,182,268_AL_.jpg"<br />},<br />....<br />]<br />}</pre> 
<p>For the response data, we need to create the below two model classes.</p> 
<p><span style="color: #0000ff;"><strong>MovieList.kt</strong></span></p> 
<pre>import com.c1ctech.mvvmwithnetworksource.Movie<br />import com.google.gson.annotations.SerializedName<br /><br />data class MovieList(@SerializedName("Search") val mList : List<;Movie>;)</pre> 
<p><span style="color: #0000ff;"><strong>Movie.kt</strong></span></p> 
<pre>import com.google.gson.annotations.SerializedName<br /><br />data class Movie(<br /> @SerializedName("Title") val title: String,<br /> @SerializedName("Poster") val poster: String,<br /> val imdbID: String,<br /> @SerializedName("Year") val year: String<br />)</pre> 
<h5><span style="color: #0000ff;"><strong>Setting up Retrofit</strong></span></h5> 
<p>Create an interface <strong><span style="color: #008000;">RetrofitService</span></strong> for the API call.</p> 
<p><span style="color: #0000ff;"><strong>RetrofitService.kt</strong></span></p> 
<pre>import com.c1ctech.mvvmwithnetworksource.model.MovieList<br />import retrofit2.Call<br />import retrofit2.Retrofit<br />import retrofit2.converter.gson.GsonConverterFactory<br />import retrofit2.http.GET<br /><br />interface RetrofitService {<br /> @GET("api?s=batman")<br /> fun getAllMovies(): Call<;MovieList>;<br /><br /> companion object {<br /><br /> var retrofitService: RetrofitService? = null<br /><br /><strong><span style="color: #008000;"> //Create the Retrofit service instance using the retrofit.</span></strong><br /> fun getInstance(): RetrofitService {<br /><br /> if (retrofitService == null) {<br /> val retrofit = Retrofit.Builder()<br /> .baseUrl("https://fake-movie-database-api.herokuapp.com/")<br /> .addConverterFactory(GsonConverterFactory.create())<br /> .build()<br /> retrofitService = retrofit.create(RetrofitService::class.java)<br /> }<br /> return retrofitService!!<br /> }<br /> }<br />}</pre> 
<h5><span style="color: #0000ff;"><strong>Setup Data Repository</strong></span></h5> 
<p>Inside the below repository class, we need to pass the retrofit service instance to perform the network call. The repository class will only interact with the network source, the response of the network call we will handle later in ViewModel.</p> 
<p><strong><span style="color: #0000ff;">MainRepository.kt</span></strong></p> 
<pre>import com.c1ctech.mvvmwithnetworksource.RetrofitService<br /><br />class MainRepository constructor(private val retrofitService: RetrofitService) {<br /><br /> fun getAllMovies() = retrofitService.getAllMovies()<br />}</pre> 
<h4><span style="color: #000080;"><strong>Setup the ViewModel</strong></span></h4> 
<p>The MainViewModel class extends the ViewModel. </p> 
<p>In the ViewModel constructor, we need to pass the data repository to handle the response from the API call.</p> 
<p>It uses LiveData to update the data to UI. LiveData only updates app component observers that are in an active lifecycle state.</p> 
<p><strong><span style="color: #0000ff;">MainViewModel.kt</span></strong></p>
<!-- WP QUADS Content Ad Plugin v. 2.0.98.1 -->
<div class="quads-location quads-ad2" id="quads-ad2" style="float:none;margin:0px;">

</div>
 
<pre>import androidx.lifecycle.MutableLiveData<br />import androidx.lifecycle.ViewModel<br />import com.c1ctech.mvvmwithnetworksource.Movie<br />import com.c1ctech.mvvmwithnetworksource.model.MovieList<br />import com.c1ctech.mvvmwithnetworksource.repository.MainRepository<br />import retrofit2.Call<br />import retrofit2.Callback<br />import retrofit2.Response<br /><br />class MainViewModel(private val repository: MainRepository) : ViewModel() {<br /><br /> val movieList = MutableLiveData<;List<;Movie>;>;()<br /> val errorMessage = MutableLiveData<;String>;()<br /><br /> fun getAllMovies() {<br /> val response = repository.getAllMovies()<br /> response.enqueue(object : Callback<;MovieList>; {<br /> override fun onResponse(call: Call<;MovieList>;, response: Response<;MovieList>;) {<br /> movieList.postValue(response.body()?.mList)<br /> }<br /><br /> override fun onFailure(call: Call<;MovieList>;, t: Throwable) {<br /> errorMessage.postValue(t.message)<br /> }<br /> })<br /> }<br />}</pre> 
<h4><span style="color: #000080;"><strong>ViewModel Factory</strong></span></h4> 
<p>To create ViewModel we have <strong><span style="color: #008000;">ViewModelProviders</span></strong> utility provided by Android. But ViewModelProviders can only instantiate ViewModels with the no-arg constructor.<br />So to create a ViewModel with multiple arguments, we need to use a Factory that we can pass to ViewModelProviders to use when an instance of MyViewModel is required.</p> 
<p><strong><span style="color: #0000ff;">MyViewModelFactory.kt</span></strong></p> 
<pre>import androidx.lifecycle.ViewModel<br />import androidx.lifecycle.ViewModelProvider<br />import com.c1ctech.mvvmwithnetworksource.repository.MainRepository<br />import com.c1ctech.mvvmwithnetworksource.viewmodel.MainViewModel<br /><br />class MyViewModelFactory constructor(private val repository: MainRepository) :<br /> ViewModelProvider.Factory {<br /> override fun <;T : ViewModel?>; create(modelClass: Class<;T>;): T {<br /> return if (modelClass.isAssignableFrom(MainViewModel::class.java)) {<br /> MainViewModel(this.repository) as T<br /> } else {<br /> throw IllegalArgumentException("ViewModel Not Found")<br /> }<br /> }<br />}</pre> 
<h4><strong><span style="color: #000080;">Setting up the UI</span></strong></h4> 
<p>In the UI part, We need to create an instance of the ViewModel and observe the API response. Based on the API response we need to update the UI.</p> 
<h5><strong><span style="color: #0000ff;">Creating Activity Layout file</span></strong></h5> 
<p>The below layout file contains a RecyclerView.</p> 
<p><strong><span style="color: #0000ff;">activity_main.xml</span></strong></p> 
<pre><;?xml version="1.0" encoding="utf-8"?>;<br /><;androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> xmlns:app="http://schemas.android.com/apk/res-auto"<br /> xmlns:tools="http://schemas.android.com/tools"<br /> android:layout_width="match_parent"<br /> android:layout_height="match_parent"<br /> tools:context=".MainActivity">;<br /><br /> <;androidx.recyclerview.widget.RecyclerView<br /> android:id="@+id/recyclerview"<br /> android:layout_width="match_parent"<br /> android:layout_height="wrap_content"<br /> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"<br /> app:layout_constraintEnd_toEndOf="parent"<br /> app:layout_constraintStart_toStartOf="parent"<br /> app:layout_constraintTop_toTopOf="parent"<br /> tools:itemCount="5"<br /> tools:listitem="@layout/layout_rv_item" />;<br /><br /><;/androidx.constraintlayout.widget.ConstraintLayout>;</pre> 
<h5><strong><span style="color: #0000ff;">Creating Adapter Layout file</span></strong></h5> 
<p>The below layout file represents the UI of each item of recyclerView.</p> 
<p><strong><span style="color: #0000ff;">layout_rv_item.xml</span></strong></p> 
<pre><;?xml version="1.0" encoding="utf-8"?>;<br /><;androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"<br /> xmlns:app="http://schemas.android.com/apk/res-auto"<br /> android:layout_width="match_parent"<br /> android:layout_height="wrap_content"<br /> android:layout_margin="8dp"<br /> android:elevation="8dp"<br /> app:cardCornerRadius="8dp">;<br /><br /> <;ImageView<br /> android:id="@+id/moviePoster"<br /> android:layout_width="match_parent"<br /> android:layout_height="200dp"<br /> android:scaleType="fitCenter" />;<br /><br /> <;TextView<br /> android:id="@+id/movieTitle"<br /> android:layout_width="match_parent"<br /> android:layout_height="wrap_content"<br /> android:background="@color/purple_700"<br /> android:gravity="center_vertical"<br /> android:padding="3dp"<br /> android:textAppearance="@style/TextAppearance.AppCompat.Large"<br /> android:textColor="@android:color/white" />;<br /><br /><;/androidx.cardview.widget.CardView>;</pre> 
<h5><strong><span style="color: #0000ff;">Creating Adapter class</span></strong></h5> 
<p>The MainAdapter is an adapter class for the recycler view to set all the items into recycler view.</p> 
<p><strong><span style="color: #0000ff;">MainAdapter.kt</span></strong></p> 
<pre>import android.view.LayoutInflater<br />import android.view.ViewGroup<br />import androidx.recyclerview.widget.RecyclerView<br />import com.bumptech.glide.Glide<br />import com.c1ctech.mvvmwithnetworksource.databinding.LayoutRvItemBinding<br /><br />class MainAdapter : RecyclerView.Adapter<;MainViewHolder>;() {<br /><br /> var movies = mutableListOf<;Movie>;()<br /><br /> fun setMovieList(movies: List<;Movie>;) {<br /> this.movies = movies.toMutableList()<br /> notifyDataSetChanged()<br /> }<br /><br /> override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {<br /> val inflater = LayoutInflater.from(parent.context)<br /><br /> val binding = LayoutRvItemBinding.inflate(inflater, parent, false)<br /> return MainViewHolder(binding)<br /> }<br /><br /> override fun onBindViewHolder(holder: MainViewHolder, position: Int) {<br /> val movie = movies[position]<br /> holder.binding.movieTitle.text = movie.title<br /> Glide.with(holder.itemView.context).load(movie.poster).placeholder(R.drawable.placeholder)<br /> .into(holder.binding.moviePoster)<br /><br /> }<br /><br /> override fun getItemCount(): Int {<br /> return movies.size<br /> }<br />}<br /><br />class MainViewHolder(val binding: LayoutRvItemBinding) : RecyclerView.ViewHolder(binding.root) {}</pre> 
<h5><strong><span style="color: #0000ff;">Creating MainActivity File</span></strong></h5> 
<p>Inside MainActivity, we will observe the response from the API and update the UI.</p> 
<p><span style="color: #0000ff;"><strong>MainActivity.kt</strong></span></p> 
<pre>import androidx.appcompat.app.AppCompatActivity<br />import android.os.Bundle<br />import android.util.Log<br />import androidx.lifecycle.Observer<br />import androidx.lifecycle.ViewModelProvider<br />import com.c1ctech.mvvmwithnetworksource.databinding.ActivityMainBinding<br />import com.c1ctech.mvvmwithnetworksource.repository.MainRepository<br />import com.c1ctech.mvvmwithnetworksource.viewmodel.MainViewModel<br /><br />class MainActivity : AppCompatActivity() {<br /> private val TAG = "MainActivity"<br /> private lateinit var binding: ActivityMainBinding<br /><br /> lateinit var viewModel: MainViewModel<br /><br /> private val retrofitService = RetrofitService.getInstance()<br /> val adapter = MainAdapter()<br /><br /> override fun onCreate(savedInstanceState: Bundle?) {<br /> super.onCreate(savedInstanceState)<br /> binding = ActivityMainBinding.inflate(layoutInflater)<br /> setContentView(binding.root)<br /><br /><strong><span style="color: #008000;"> //get viewmodel instance using MyViewModelFactory</span></strong><br /> viewModel =<br /> ViewModelProvider(this, MyViewModelFactory(MainRepository(retrofitService))).get(<br /> MainViewModel::class.java<br /> )<br /><br /><strong><span style="color: #008000;"> //set recyclerview adapter</span></strong><br /> binding.recyclerview.adapter = adapter<br /><br /> viewModel.movieList.observe(this, Observer {<br /> Log.d(TAG, "movieList: $it")<br /> adapter.setMovieList(it)<br /> })<br /><br /> viewModel.errorMessage.observe(this, Observer {<br /> Log.d(TAG, "errorMessage: $it")<br /> })<br /><br /> viewModel.getAllMovies()<br /> }<br />}</pre> 
<p>When you run the app it will look like this:</p> 
<p><img class="alignnone wp-image-3112" src="https://c1ctech.com/wp-content/uploads/2022/02/Screenshot_20220221-161915_MVVMWithNetworkSource-498x1024.jpg" alt="" width="362" height="745" /></p> 
<p> ;</p> 


