Simple example of MVVM architecture in Kotlin

whatminjacodes [she/they] - Jan 12 '21 - - Dev Community

// EDIT 2024: I have made an example project that is using Jetpack Compose so check it out from my GitHub (whatminjacodes) if you are interested!


This is a super small and simple example project for showing how Model-View-ViewModel (MVVM) architecture can be implemented in Kotlin!

I feel quite often that even the simple example projects have many unnecessary libraries or features so I wanted to do (almost) as simple project as possible! I did add fragments here but it's only because I personally like to use them. If you don't want to use fragments, just add what's in MainFragment to MainActivity instead!

This project only contains MainActivity, MainFragment, MainViewModel and DataModel. MainActivity opens MainFragment which contains text and one button. When that button is clicked, MainViewModel gets the updated text from DataModel and triggers an UI update in MainFragment using LiveData.

Screenshot of the app

Let's get started!

Create a new project

Create a new Android Studio project with Empty Activity and go to the dependencies (build.gradle file). Add the needed dependencies.

    def lifecycle_version = "2.2.0"
    def fragments_version = "1.2.5"

    // viewmodel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

    // fragments
    implementation "androidx.fragment:fragment-ktx:$fragments_version"
Enter fullscreen mode Exit fullscreen mode

Here's an Android documentation which explains more about:
ViewModels
Fragments

I'm adding View Binding here because it makes writing code that interacts with views a lot easier. You can read more about it from here. But it basically just removes the need to use findViewById() and using View Binding is the recommended way to do it since Kotlin extensions are getting deprecated this year.

Project structure

Screenshot of the structure of the app

Here's an image of the project structure. All data related files should go into model, all UI related files to view and all ViewModels into viewmodel.

Add MainActivity.kt and activity_main.xml

Let's add MainActivity and it's layout next!

package com.minjee.basicmvvmexample.view

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.add
import androidx.fragment.app.commit
import com.minjee.basicmvvmexample.R

/*
 *      MainActivity
 *      - opens our fragment which has the UI
 */
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState == null) {

            // Adds our fragment
            supportFragmentManager.commit {
                setReorderingAllowed(true)
                add<MainFragment>(R.id.fragment_container_view)
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

MainActivity basically only opens our MainFragment. I like using fragments so if you don't want that, just normally create an activity and add the code we write in MainFragment in MainActivity instead! You might have to do some small changes but the basic idea is same.

<!-- Has the fragment container view for displaying our fragment -->
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
Enter fullscreen mode Exit fullscreen mode

We just have a container for displaying fragments here. If you don't want to use fragments, then just add what we write in fragment_main.xml in activity_main.xml instead. You might have to do some small changes here too but again, the basic idea is same.

Add MainFragment.kt and fragment_main.xml

Next we add a fragment and it's layout.

package com.minjee.basicmvvmexample.view

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.minjee.basicmvvmexample.R
import com.minjee.basicmvvmexample.databinding.FragmentMainBinding
import com.minjee.basicmvvmexample.viewmodel.MainViewModel

/*
 *      MainFragment
 *      - shows the UI
 *      - listens to viewModel for updates on UI
 */
class MainFragment: Fragment() {

    // View Binding
    private var _binding: FragmentMainBinding? = null
    private val binding get() = _binding!!

    // Create a viewModel
    private val viewModel: MainViewModel by activityViewModels()

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMainBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        setupClickListeners()
        fragmentTextUpdateObserver()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    // Setup the button in our fragment to call getUpdatedText method in viewModel
    private fun setupClickListeners() {
        binding.fragmentButton.setOnClickListener { viewModel.getUpdatedText() }
    }

    // Observer is waiting for viewModel to update our UI
    private fun fragmentTextUpdateObserver() {
        viewModel.uiTextLiveData.observe(viewLifecycleOwner, Observer { updatedText ->
            binding.fragmentTextView.text = updatedText
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have a bit more code here. I tried to comment it so it's easier to follow!

First we do the view binding so we get reference to xml layout objects easier. Then we create a viewModel and after that we have the basic fragment functions (onCreateView(), onViewCreated() and onDestroyView()).

The first code we add ourself is setupClickListeners(). Here we just get a reference to the button in our UI and setup it to use a function that's in viewModel. This is because there should only be UI logic inside MainFragment. MainFragment doesn't know what data we are updating into our UI. It only cares the text gets updated when the button is clicked and viewModel is taking care of the rest of the logic.

Then we have an observer which is listening to updates that viewModel is triggering. This function will do the actual update of the UI text, but again it doesn't care what it's inserting there. It just knows it's supposed to do an update.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/fragmentTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="You have just opened a fragment!"
        android:textSize="24sp"
        android:padding="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/fragmentButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Update text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/fragmentTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
Enter fullscreen mode Exit fullscreen mode

This is just a simple xml layout which has one button and a textView.

Add MainViewModel.kt and DataModel.kt

Then we are ready to add viewModel and model!

package com.minjee.basicmvvmexample.model

// Contains the data we want to show on UI (in MainFragment)
data class DataModel(val textForUI: String)
Enter fullscreen mode Exit fullscreen mode

DataModel is just a simple data container class which has a String value that we are displaying on UI after clicking the button.

package com.minjee.basicmvvmexample.viewmodel

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.minjee.basicmvvmexample.model.DataModel

/*
 *      MainViewModel
 *      - viewModel that updates the MainFragment (the visible UI)
 *      - gets the data from model
 */
class MainViewModel: ViewModel() {

    // Create the model which contains data for our UI
    private val model = DataModel(textForUI = "Here's the updated text!")

    // Create MutableLiveData which MainFragment can subscribe to
    // When this data changes, it triggers the UI to do an update
    val uiTextLiveData = MutableLiveData<String>()

    // Get the updated text from our model and post the value to MainFragment
    fun getUpdatedText() {
        val updatedText = model.textForUI
        uiTextLiveData.postValue(updatedText)
    }
}
Enter fullscreen mode Exit fullscreen mode

So like I said earlier, viewModel is taking care of the logic that's not directly related to UI objects. First we create a DataModel so we can have a text that's going to get updated after clicking a button.

Next we add a MutableLiveData which will trigger an update for our observer that we created in MainFragment. Observer will get triggered after calling postValue() in getUpdatedText() function and then the MainFragment can update the UI text element with the updated data.

Summary

So that's it! You can find the full code and working project from my Github. I wanted to write this blog post and create the project in GitHub because it can then just be easily cloned so there's no need to write all this from scratch each time when starting a new project :) I hope you also got something out of this!

Give me a follow if you want to see more tutorials! You can also follow my Instagram whatminjaplays if you are interested to see more about my days as a software developer and a gaming enthusiast!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .