How to Build a Food and Their Price List Android App using MVVM Architecture with Kotlin?
Last Updated :
08 Apr, 2023
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
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"
implementation( "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" )
implementation( "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" )
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()
.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
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
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:
Like Article
Suggest improvement
Share your thoughts in the comments
Please Login to comment...