How to Build a Food and Their Price List Android App using MVVM Architecture with Kotlin?
In this article, we will food app that displays a list of foods and their price using the retrofit library and MVVM architecture. Model — View — ViewModel (MVVM) is the industry-recognized software architecture pattern that overcomes all drawbacks of MVP and MVC design patterns. MVVM suggests separating the data presentation logic(Views or UI) from the core business logic part of the application. A sample video is given below to get an idea about what we are going to do in this article.
The separate code layers of MVVM are:
- Model: This layer is responsible for the abstraction of the data sources. Model and ViewModel work together to get and save the data.
- View: The purpose of this layer is to inform the ViewModel about the user’s action. This layer observes the ViewModel and does not contain any kind of application logic.
- ViewModel: It exposes those data streams which are relevant to the View. Moreover, it serves as a link between the Model and the View.
Step-by-Step Implementation
Step 1: Create a New Project in Android Studio
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Kotlin as the programming language.
Step 2: Add Required Dependencies
Add Retrofit dependencies in Build.gradle(app) file
Kotlin
// retrofit implementation( "com.squareup.retrofit2:retrofit:2.9.0" ) implementation 'com.squareup.retrofit2:converter-gson:2.3.0' |
Add View Model and Live Data dependencies in Build.gradle(app) file
Kotlin
def lifecycle_version = "2.6.0-alpha01" // ViewModel implementation( "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" ) // ViewModel utilities for Compose implementation( "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" ) // LiveData implementation( "androidx.lifecycle:lifecycle-live data-ktx:$lifecycle_version" ) |
Add Glide Dependency in Build.gradle(app) file
Kotlin
implementation 'com.github.bumptech.glide:glide:4.13.2' |
Glide library helps in image processing in android.
Step 3: Enable View Binding
To enable view binding add this code inside the android {} block in build.gradle(app) file
Kotlin
buildFeatures { viewBinding = true } |
Step 4: Generate data classes
Right-click on the root package and select New > Kotlin-data class from JSON. If you don’t have this plugin, go to File -> Settings -> Plugin and install JSON to Kotlin Plugin. Copy the JSON result and paste it. Give this file a suitable name after that data classes will be generated.
Kotlin
data class Data( val CatId: String, val CatName: String, val LongDescription: String, val MasterCatId: String, val MasterCatName: String, val PrdBrandId: String, val PrdBrandName: String, val PrdId: String, val PrdImageUrl: String, val PrdName: String, val PrdPrice: String, val PrdPriceShow: String, val PrdRating: String, val PrdratingCount: String, val ProductImages: List<ProductImage>, val ProductTypeId: String, val ProductTypeImageurl: String, val ProductTypeName: String, val PtrPrdvariantList: List<PtrPrdvariant>, val ShortDescription: String, val SubCatId: String, val SubCatName: String ) |
Kotlin
data class MealList( val Data: List<Data> ) |
Right Click on the Root package and create an interface APi
Kotlin
import com.room.daisoftware.pojo.MealList import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query interface Api { @GET ( "Fetch_ProductList_By_PartnerId_SubCatId?" ) fun getData( @Query ( "SubCatId" ) Sid:String , @Query ( "PartnerId" ) Pid:String ):Call<MealList> } |
Create a Retrofit Instance
Kotlin
import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory object RetrofitInstance { val api:Api by lazy { Retrofit.Builder() .baseUrl( "https://www.deshizon.com/api/" ) .addConverterFactory(GsonConverterFactory.create()) .build() .create(Api:: class .java) } } |
Step 5: Design layout Files
Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. Comments are added inside the code to understand the code in more detail.
XML
<? xml version = "1.0" encoding = "utf-8" ?> < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" android:background = "#EAEAEA" android:orientation = "vertical" tools:context = ".activities.MainActivity" > < androidx.recyclerview.widget.RecyclerView android:id = "@+id/rcv" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_margin = "5dp" android:layout_weight = "1.7" tools:listitem = "@layout/items" /> < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_weight = "0.3" android:orientation = "horizontal" > </ LinearLayout > </ LinearLayout > |
Since we are using recycler view we need to create a new layout file for that go to res > layout and create a new Layout resource file named items.xml
XML
<? xml version = "1.0" encoding = "utf-8" ?> < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:app = "http://schemas.android.com/apk/res-auto" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "wrap_content" > < androidx.cardview.widget.CardView android:id = "@+id/cardView" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_margin = "10dp" android:elevation = "10dp" app:cardCornerRadius = "15dp" > < LinearLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:orientation = "horizontal" > < ImageView android:id = "@+id/imageView" android:layout_width = "80dp" android:layout_height = "80dp" android:layout_margin = "10dp" tools:srcCompat = "@tools:sample/avatars" /> < LinearLayout android:layout_width = "wrap_content" android:layout_height = "match_parent" android:layout_marginStart = "30dp" android:orientation = "vertical" > < TextView android:id = "@+id/textView" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "8dp" android:text = "TextView" android:textColor = "@color/black" android:textSize = "13sp" android:textStyle = "bold" /> < LinearLayout android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:orientation = "horizontal" > < TextView android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "8dp" android:text = "price - " android:textColor = "#D13838" android:textSize = "13sp" android:textStyle = "bold" /> < TextView android:id = "@+id/price" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "8dp" android:text = "TextView" android:textColor = "#D13838" android:textSize = "13sp" android:textStyle = "bold" /> </ LinearLayout > </ LinearLayout > </ LinearLayout > </ androidx.cardview.widget.CardView > </ LinearLayout > |
Step 6: Create an ApiAdapter class for RecyclerView
Kotlin
import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.room.daisoftware.databinding.ItemsBinding import com.room.daisoftware.pojo.Data import com.room.daisoftware.pojo.MealList class ApiAdapter(): RecyclerView.Adapter<ApiAdapter.ApiViewHolder>() { private var foodList = ArrayList<Data>() fun setMealList(charList : List<Data>){ this .foodList = charList as ArrayList<Data> notifyDataSetChanged() } class ApiViewHolder(val binding: ItemsBinding):RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ApiViewHolder { return ApiViewHolder(ItemsBinding.inflate(LayoutInflater.from(parent.context),parent, false )) } override fun onBindViewHolder(holder: ApiViewHolder, position: Int) { Glide.with(holder.itemView) .load(foodList[position].PrdImageUrl) .into(holder.binding.imageView) holder.binding.textView.text = foodList[position].PrdName holder.binding.price.text = foodList[position].PrdPrice } override fun getItemCount(): Int { return foodList.size } } |
Step 7: Create Meal View Model
Since we are using MVVM architecture we need to create a View Model Class with live data in it.
Kotlin
import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.room.daisoftware.pojo.Data import com.room.daisoftware.pojo.MealList import com.room.daisoftware.retrofit.RetrofitInstance import retrofit2.Call import retrofit2.Callback import retrofit2.Response class MealViewModel : ViewModel() { private var MealLiveData = MutableLiveData<List<Data>>() fun getMeals() { RetrofitInstance.api.getData( "507" , "180" ).enqueue(object : Callback<MealList> { override fun onResponse(call: Call<MealList>, response: Response<MealList>) { if (response.body() != null ) { MealLiveData.value = response.body()!!.Data } else { return } } override fun onFailure(call: Call<MealList>, t: Throwable) { Log.d( "TAG" , t.message.toString()) } }) } fun ObserveMealLiveData(): LiveData<List<Data>> { return MealLiveData } } |
Step 8: Write Code for UI
In the end, we will write code for the main activity and meal activity which represents our UI
MainActivity.kt file:
Kotlin
import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.google.firebase.firestore.ktx.firestore import com.google.firebase.ktx.Firebase import com.room.daisoftware.adapter.ApiAdapter import com.room.daisoftware.databinding.ActivityMainBinding import com.room.daisoftware.viewModel.MealViewModel class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var ApiAdapter:ApiAdapter private lateinit var mealViewModel:MealViewModel override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) prepareRecyclerView() mealViewModel = ViewModelProvider( this )[MealViewModel:: class .java] mealViewModel.getMeals() mealViewModel.ObserveMealLiveData().observe( this , Observer { ApiAdapter.setMealList(it) }) } private fun prepareRecyclerView() { ApiAdapter = ApiAdapter() binding.rcv.apply { layoutManager = LinearLayoutManager(applicationContext) adapter = ApiAdapter } } } |
Output:
Contact Us