-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8830 from mrkc2303/Android_Development_With_Kotlin
Create 19.01 Overview of MVVM architecture in Android.md
- Loading branch information
Showing
1 changed file
with
345 additions
and
0 deletions.
There are no files selected for viewing
345 changes: 345 additions & 0 deletions
345
..._Development_with_Kotlin/19. Architecture/19.1 Overview of MVVM architecture.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,345 @@ | ||
# Overview of MVVM architecture in Android | ||
|
||
Developers always prefer a clean and structured code for the projects. By organizing the codes according to a design pattern helps in the maintenance of the software. | ||
|
||
MVVM architecture is a Model-View-ViewModel architecture that removes the tight coupling between each component. Most importantly, in this architecture, the children don’t have a direct reference to the parent, they only have the reference by observables. | ||
|
||
![MVVM](https://media.geeksforgeeks.org/wp-content/uploads/20201002215007/MVVMSchema.png) | ||
|
||
|
||
## What is MVVM? | ||
|
||
MVVM stands for Model, View, ViewModel. | ||
|
||
| ![MVVM](https://i1.wp.com/howtodoandroid.com/wp-content/uploads/2021/03/mvvm-architecture.png?resize=768%2C576&ssl=1) | | ||
|:--:| | ||
| MVVM Structure | | ||
|
||
1. **Model:** | ||
|
||
This holds the data of the application. It cannot directly talk to the View. Generally, it’s recommended to expose the data to the ViewModel through Observables | ||
|
||
1. **View:** | ||
|
||
It represents the UI of the application devoid of any Application Logic. It observes the ViewModel. | ||
|
||
1. **ViewModel:** | ||
|
||
It acts as a link between the Model and the View. It’s responsible for transforming the data from the Model. It provides data streams to the View. It also uses hooks or callbacks to update the View. It’ll ask for the data from the Model. | ||
A ViewModel is always created in association with a scope (an fragment or an activity) and will be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished. | ||
|
||
In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a configuration change (e.g. rotation). The new instance of the owner will just re-connected to the existing ViewModel. | ||
|
||
| ![View Model Lifecycle Scope](https://i0.wp.com/howtodoandroid.com/wp-content/uploads/2021/03/view-model-scope.png?w=522&ssl=1 "View Model Lifecycle Scope") | | ||
|:--:| | ||
| View Model Lifecycle Scope | | ||
|
||
|
||
MVVM pattern has some similarities with the MVP(Model — View — Presenter) design pattern as the Presenter role is played by ViewModel. However, the drawbacks of the MVP pattern has been solved by MVVM in the following ways: | ||
|
||
1. ViewModel does not hold any kind of reference to the View. | ||
1. Many to 1 relationship exist between View and ViewModel. | ||
1. No triggering methods to update the View. | ||
|
||
## Implementation of MVVM | ||
|
||
### Adding dependencies for MVVM | ||
|
||
Before starting with the implementations, first let us add the following dependencies in your app level `build.gradle`. | ||
|
||
``` | ||
//ViewModel and livedata | ||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" | ||
implementation 'com.google.code.gson:gson:2.8.6' | ||
implementation 'com.squareup.retrofit2:retrofit:2.9.0' | ||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' | ||
implementation 'com.github.bumptech.glide:glide:4.12.0' | ||
kapt 'com.github.bumptech.glide:compiler:4.12.0' | ||
``` | ||
|
||
Also, don’t forget to add a internet permission in your `manifeats.xml` file. | ||
|
||
``` | ||
<uses-permission android:name="android.permission.INTERNET"/> | ||
``` | ||
|
||
|
||
### Creating the model | ||
|
||
Create the app model. Also referred to as the data class. To avoid confusion, create a package named model inside the java folder. Then, create a data class named Blog in the model package, as shown below. | ||
|
||
For simplicity, the data class will only have one variable (title). There is no need to add getters and setters; Kotlin adds them to the class automatically. | ||
|
||
``` | ||
data class Blog( | ||
var title:String | ||
) | ||
``` | ||
|
||
### Creating the view | ||
|
||
The view is what the user sees on the screen. The UI, therefore, needs to be well structured to minimize any confusion and dissatisfaction. | ||
|
||
Open `activity_main.xml` file and change the Layout from constraint to linear Layout. Set the orientation to vertical; this arranges the UI components vertically in the Layout. Our app’s primary widgets are `Edittext`, `Button`, and a `RecyclerView`. | ||
|
||
Make sure all these widgets have IDs since they will be vital at a later stage. This is how our `activity_main.xml` file should look like. | ||
|
||
``` | ||
<?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:orientation="vertical" | ||
android:layout_height="match_parent" | ||
tools:context=".MainActivity"> | ||
<EditText | ||
android:id="@+id/titletxt" | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:layout_marginTop="24dp" | ||
android:ems="10" | ||
android:textColor="#000" | ||
android:inputType="textPersonName" | ||
android:hint="Enter Information" | ||
android:textAlignment="center" | ||
app:layout_constraintBottom_toTopOf="@+id/button" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintHorizontal_bias="0.497" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toTopOf="parent" /> | ||
<Button | ||
android:id="@+id/button" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_marginBottom="40dp" | ||
android:backgroundTint="@color/colorAccent" | ||
android:layout_marginTop="20dp" | ||
android:text="Submit" | ||
android:layout_gravity="center" | ||
app:layout_constraintBottom_toTopOf="@+id/recycler" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintHorizontal_bias="0.498" | ||
app:layout_constraintStart_toStartOf="parent" /> | ||
<androidx.recyclerview.widget.RecyclerView | ||
android:id="@+id/recycler" | ||
android:background="#E8DDDD" | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
app:layout_constraintBottom_toBottomOf="parent" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" /> | ||
</LinearLayout> | ||
``` | ||
|
||
### Creating the item_view | ||
|
||
Still on the Layout, we need to create the design of the element shown in the `RecyclerView`. Therefore, create a file named `item.xml` and add the code shown in the image below. The design is simple since the user can also access one attribute from the data class. | ||
|
||
``` | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<androidx.cardview.widget.CardView | ||
xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
android:layout_width="match_parent" | ||
android:layout_margin="10dp" | ||
android:layout_height="wrap_content"> | ||
<RelativeLayout | ||
android:layout_width="match_parent" | ||
android:layout_height="100dp" | ||
android:orientation="horizontal"> | ||
<TextView | ||
android:id="@+id/title" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_alignParentTop="true" | ||
android:layout_alignParentBottom="true" | ||
android:layout_marginTop="31dp" | ||
android:layout_marginEnd="276dp" | ||
android:layout_marginBottom="30dp" | ||
android:padding="10dp" | ||
android:text="Hallo" | ||
android:textColor="#000" /> | ||
<ImageButton | ||
android:id="@+id/delete" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_alignParentLeft="true" | ||
android:layout_alignParentTop="true" | ||
android:layout_alignParentEnd="true" | ||
android:layout_alignParentBottom="true" | ||
android:layout_marginLeft="328dp" | ||
android:backgroundTint="#ffff" | ||
android:layout_marginTop="22dp" | ||
android:layout_marginEnd="16dp" | ||
android:layout_marginBottom="35dp" | ||
android:src="@drawable/ic_baseline_delete_24" /> | ||
</RelativeLayout> | ||
</androidx.cardview.widget.CardView> | ||
``` | ||
|
||
|
||
### Create a RecyclerView Adapter | ||
|
||
|
||
A `RecyclerView` adapter handles the binding of the `item.xml` layout to the `RecyclerView`. It also takes in a list of items and displays them to the user. The code for the `RecyclerView` adapter is shown below. | ||
|
||
``` | ||
class NoteRecyclerAdapter(val viewModel: MainViewModel, val arrayList: ArrayList<Blog>, val context: Context): RecyclerView.Adapter<NoteRecyclerAdapter.NotesViewHolder>() { | ||
override fun onCreateViewHolder( | ||
parent: ViewGroup, | ||
viewType: Int, | ||
): NoteRecyclerAdapter.NotesViewHolder { | ||
var root = LayoutInflater.from(parent.context).inflate(R.layout.item,parent,false) | ||
return NotesViewHolder(root) | ||
} | ||
override fun onBindViewHolder(holder: NoteRecyclerAdapter.NotesViewHolder, position: Int) { | ||
holder.bind(arrayList.get(position)) | ||
} | ||
override fun getItemCount(): Int { | ||
if(arrayList.size==0){ | ||
Toast.makeText(context,"List is empty",Toast.LENGTH_LONG).show() | ||
}else{ | ||
} | ||
return arrayList.size | ||
} | ||
inner class NotesViewHolder(private val binding: View) : RecyclerView.ViewHolder(binding) { | ||
fun bind(blog: Blog){ | ||
binding.title.text = blog.title | ||
binding.delete.setOnClickListener { | ||
viewModel.remove(blog) | ||
notifyItemRemoved(arrayList.indexOf(blog)) | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Creating the ViewModel | ||
|
||
Create a package named `ViewModel`. Inside this folder, create a Kotlin class and name it `MainViewModel`. The class should extend the Android `ViewModel`. You might face an error if you failed to add lifecycle dependencies from Jetpack. | ||
|
||
The `MainViewModel` will have a mutable livedata item that holds the array list. It’s vital to use LiveData since it notifies the UI in case of any data change. The `MainViewModel` code is shown below. | ||
|
||
``` | ||
class MainViewModel: ViewModel() { | ||
var lst = MutableLiveData<ArrayList<Blog>>() | ||
var newlist = arrayListOf<Blog>() | ||
fun add(blog: Blog){ | ||
newlist.add(blog) | ||
lst.value=newlist | ||
} | ||
fun remove(blog: Blog){ | ||
newlist.remove(blog) | ||
lst.value=newlist | ||
} | ||
} | ||
``` | ||
|
||
### Create the ViewModel Factory | ||
|
||
The purpose of a `ViewModel` factory is to instantiate the `ViewModel`. This prevents the app from crashing in case an activity is not found. | ||
|
||
The code for our `MainViewModelFactory` is shown below. | ||
|
||
``` | ||
class MainViewModelFactory(): ViewModelProvider.Factory{ | ||
override fun <T : ViewModel?> create(modelClass: Class<T>): T { | ||
if(modelClass.isAssignableFrom(MainViewModel::class.java)){ | ||
return MainViewModel() as T | ||
} | ||
throw IllegalArgumentException ("UnknownViewModel") | ||
} | ||
} | ||
``` | ||
|
||
### MainActivity (connecting the code) | ||
|
||
We have created the model, `ViewModel`, `ViewModelfactory`, and `RecyclerView`. These components need to be instantiated in the `MainActivity` class for the application to work. | ||
|
||
Start by declaring the `RecyclerView` and instantiating it. Set the layout manager for the `RecyclerView` to `LinearLayoutManager`. The `MainActivity` file contains three major methods; `initialiseAdapter`,`observeData`, and `addData`. the `initialiseAdapter` method assigns a `ViewManager` to the `RecyclerView`. | ||
|
||
The observeData function looks for changes in the viewmodel and forwards them to the RecyclerAdapter. The addData method takes in the user’s input and updates the list in the ViewModel. | ||
|
||
|
||
``` | ||
class MainActivity : AppCompatActivity() { | ||
private var viewManager = LinearLayoutManager(this) | ||
private lateinit var viewModel: MainViewModel | ||
private lateinit var mainrecycler: RecyclerView | ||
private lateinit var but: Button | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_main) | ||
mainrecycler = findViewById(R.id.recycler) | ||
val application = requireNotNull(this).application | ||
val factory = MainViewModelFactory() | ||
viewModel = ViewModelProviders.of(this,factory).get(MainViewModel::class.java) | ||
but = findViewById(R.id.button) | ||
but.setOnClickListener { | ||
addData() | ||
} | ||
initialiseAdapter() | ||
} | ||
private fun initialiseAdapter(){ | ||
mainrecycler.layoutManager = viewManager | ||
observeData() | ||
} | ||
fun observeData(){ | ||
viewModel.lst.observe(this, Observer{ | ||
Log.i("data",it.toString()) | ||
mainrecycler.adapter= NoteRecyclerAdapter(viewModel, it, this) | ||
}) | ||
} | ||
fun addData(){ | ||
var txtplce = findViewById<EditText>(R.id.titletxt) | ||
var title=txtplce.text.toString() | ||
if(title.isNullOrBlank()){ | ||
Toast.makeText(this,"Enter value!",Toast.LENGTH_LONG).show() | ||
}else{ | ||
var blog= Blog(title) | ||
viewModel.add(blog) | ||
txtplce.text.clear() | ||
mainrecycler.adapter?.notifyDataSetChanged() | ||
} | ||
} | ||
} | ||
``` | ||
|
||
|
||
### Results | ||
|
||
If there were no errors in your code, it should compile and show the UI in the image below. Whatever you type in the `EditTex`t field should display in the `recyclerview` once you click the submit button. | ||
|
||
| ![Output](https://user-images.githubusercontent.com/67221487/143891616-ecbb1956-3c4a-4839-8580-02110bffba86.png) | | ||
| :--: | | ||
| **OUTPUT** | | ||
|
||
## Conclusion | ||
|
||
MVVM architecture has made it easier to build complex applications. As shown, it’s easier to identify bugs due to the separation of business logic from the UI code. The architecture also prevents data loss during configuration changes. Ensure that all dependencies are present before using MVVM. This measure helps prevent runtime errors. | ||
|
||
## Author | ||
|
||
- [Kanishk Chhabra](https://github.com/mrkc2303) For Documentation |