Model – View – ViewModel is an architectural pattern which will empower you to write manageable, maintainable, cleaner and testable code. MVVM is also supported and encouraged by Google itself. There are many first-party libraries like lifecycle-aware components, LiveData, ViewModel and many more.

In the previous post, you learned the theory behind MVVM. If you haven’t read it yet, I recommend you to do so as I will skip explaining the “big picture” in this tutorial.

Today you are going to put MVVM pattern into practice. You will build a simple, yet real-enough app which will make you understand MVVM on a deeper level. It will be an app displaying quotes which you put in. This quote app will have a ViewModel, Repository, fake database and a simple dependency injection. This will give you a strong foundation to build on.

Do you prefer video tutorials?

 

?Get the code from this tutorial?

Starting out

Create a new Android project with an Empty Activity as the starting point. Also make sure that you select Kotlin as the default language. The name of the launcher Activity should be QuotesActivity.

With the project created, let’s get into importing a bunch of libraries. Open build.gradle for the app module. And add the following packages. Keep in mind that the versions will change in the future, so keep them up to date.

...
dependencies {
    ...
    implementation 'androidx.appcompat:appcompat:1.0.0-rc01'
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0-rc01"
    implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
    ...
}
...

This tutorial is not focused on building a nice UI  so this is what I came up with. The QuotesActivity will have a TextView which displays all the quotes, two EditTexts for adding new quotes and a button. There is no RecyclerView for the sake of simplicity.

activity_quotes.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.quotes.QuotesActivity">

    <TextView
        android:id="@+id/textView_quotes"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintHeight_percent="0.55"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:scrollbars="vertical"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        tools:text="I like pineapples. - Thomas Jefferson"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <EditText
        android:id="@+id/editText_quote"
        android:layout_width="0dp"
        app:layout_constraintWidth_percent="0.7"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:hint="Quote"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView_quotes"
        app:layout_constraintVertical_bias="0.0" />

    <EditText
        android:id="@+id/editText_author"
        android:layout_width="0dp"
        app:layout_constraintWidth_percent="0.7"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:hint="Author"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText_quote"
        app:layout_constraintVertical_bias="0.0" />

    <Button
        android:id="@+id/button_add_quote"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:backgroundTint="?colorAccent"
        android:text="Add Quote"
        android:textColor="@android:color/white"
        app:layout_constraintBottom_toBottomOf="@+id/editText_author"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toEndOf="@+id/editText_quote"
        app:layout_constraintTop_toTopOf="@+id/editText_quote"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintWidth_percent="0.25" />
</androidx.constraintlayout.widget.ConstraintLayout>

Project structure

Before you get started writing code, it’s important to organize things into packages in a way that makes sense. This is how the project structure will look like once you have all the classes created. For now, it will be enough that you have the packages and also move QuotesActivity to ui.quotes package. Later, when you are creating new classes, refer to this picture to find their package if you’re not sure.

Making the Quote data class

This whole app is centered around quotes so it’s probably a good idea to create a class representing a single quote.

data class Quote(val quoteText: String,
                 val author: String) {

    override fun toString(): String {
        return "$quoteText - $author"
    }
}

Data Access Object – DAO for Quotes

When you have a Quote object in memory, you’d usually want to store it in a database to make it persist when the app is closed. You could use any kind of a database from Cloud Firestore to local SQLite or you could even set up your own backend, communicate with it through an API and use SQLite as a local cache. If you want to use SQLite in your real apps, check out a library called ROOM. It will make your life easier.

In the world of ROOM, anytime you want to do something in a database, you do it through a DAO. Under normal circumstances, a DAO is an interface defining all allowed actions which can happen for a table in the database, like reads and writes.

However, this tutorial is focused on the core concepts of MVVM architecture. Adding a real database of any kind would be only unnecessarily creating complexity. This is why you will use a fake database and a fake DAO which will save data to a MutableList. You are not going to skip any important steps which are present in production apps and the code will be simple at the same time.

class FakeQuoteDao {
    // A fake database table
    private val quoteList = mutableListOf<Quote>()
    // MutableLiveData is from the Architecture Components Library
    // LiveData can be observed for changes
    private val quotes = MutableLiveData<List<Quote>>()

    init {
        // Immediately connect the now empty quoteList
        // to the MutableLiveData which can be observed
        quotes.value = quoteList
    }

    fun addQuote(quote: Quote) {
        quoteList.add(quote)
        // After adding a quote to the "database",
        // update the value of MutableLiveData
        // which will notify its active observers
        quotes.value = quoteList
    }

    // Casting MutableLiveData to LiveData because its value
    // shouldn't be changed from other classes
    fun getQuotes() = quotes as LiveData<List<Quote>>
}

Database class as a container for DAOs

In a production app you will surely have more than one DAO. For example, in a chat app you may keep track of the users and also of user groups. There you have already 2 DAOs.

To put all of the data access objects in one place you should create a database class. If you are using the ROOM library, having a database class is actually required.

Because it doesn’t make sense to have 2 instances of database at the same time, a database class will be a singleton. Kotlin has a nice syntax for singletons where instead of class you write object. While this would be sufficient in our case, it usually isn’t for production apps. When you use the object keyword, you don’t have a chance to pass something into the class’ constructor. In the case of ROOM, you need to pass an application context to your database. To circumvent this problem, you have to create singletons the Java way even in Kotlin.

// Private primary constructor inaccessible from other classes
class FakeDatabase private constructor() {

    // All the DAOs go here!
    var quoteDao = FakeQuoteDao()
        private set

    companion object {
        // @Volatile - Writes to this property are immediately visible to other threads
        @Volatile private var instance: FakeDatabase? = null

        // The only way to get hold of the FakeDatabase object
        fun getInstance() =
                // Already instantiated? - return the instance
                // Otherwise instantiate in a thread-safe manner
                instance ?: synchronized(this) {
                    // If it's still not instantiated, finally create an object
                    // also set the "instance" property to be the currently created one
                    instance ?: FakeDatabase().also { instance = it }
                }
    }
}

Repository as a point of control

Repositories are classes which do all of the decision making regarding app’s data. Should you fetch new data from the server or is it enough to use the local data? Do you need to keep 5 days of weather data localy or only 3 days? Making such decisions is the job of a repository.

I know, having a repository class in this particular project may seem redundant – you only have a single DAO and you don’t even have a backend. While this is true, this doesn’t stop you from learning about the core work repositories have to do – being the single source of truth for all the data which ViewModels request.

Again, it doesn’t make sense to have multiple repository objects, so it will be a singleton. This time you need to pass in the FakeQuoteDao for repository to fulfill its role. You will use dependency injection to supply this FakeQuoteDao instance to the repository.

// FakeQuoteDao must be passed in - it is a dependency
// You could also instantiate the DAO right inside the class without all the fuss, right?
// No. This would break testability - you need to be able to pass a mock version of a DAO
// to the repository (e.g. one that upon calling getQuotes() returns a dummy list of quotes for testing)
// This is the core idea behind DEPENDENCY INJECTION - making things completely modular and independent.
class QuoteRepository private constructor(private val quoteDao: FakeQuoteDao) {

    // This may seem redundant.
    // Imagine a code which also updates and checks the backend.
    fun addQuote(quote: Quote) {
        quoteDao.addQuote(quote)
    }

    fun getQuotes() = quoteDao.getQuotes()

    companion object {
        // Singleton instantiation you already know and love
        @Volatile private var instance: QuoteRepository? = null

        fun getInstance(quoteDao: FakeQuoteDao) =
                instance ?: synchronized(this) {
                    instance ?: QuoteRepository(quoteDao).also { instance = it }
                }
    }
}

Getting closer to user interface

You have done quite a bit of work already. Now it’s time to connect what you created to the “view” part of MVVM, in this case the QuoteActivity. Activities and Fragments are merely for displaying things on the screen and for handling user input. All of the logic, data, manipulation with the data goes to a ViewModel. Then the View only calls functions on the ViewModel. This way, the data doesn’t get reset when an orientation change occurs and so on.

// QuoteRepository dependency will again be passed in the
// constructor using dependency injection
class QuotesViewModel(private val quoteRepository: QuoteRepository)
    : ViewModel() {

    fun getQuotes() = quoteRepository.getQuotes()

    fun addQuote(quote: Quote) = quoteRepository.addQuote(quote)
}

Alright! Now get the dependency injection going, then jump right into the QuotesActivity and make things work! Ummm, not yet. As you can see, QuotesViewModel requires a repository to function and that repository is passed into the constructor. The way that ViewModels are created / gotten from ViewModelProvider (to prevent recreation on, say, orientation changes)  requires a ViewModelFactory class. You simply cannot create ViewModels directly, instead, they are going to be created in a factory.

// The same repository that's needed for QuotesViewModel
// is also passed to the factory
class QuotesViewModelFactory(private val quoteRepository: QuoteRepository)
    : ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return QuotesViewModel(quoteRepository) as T
    }
}

Dependency injection ?

As I’ve already briefly mentioned, dependency injection is a way to modularize your code. In production, you want your ViewModel to operate with a real repository which is fetching data from servers and all that. However, when you are just testing your ViewModel, it’s a good idea to provide it with only some dummy data so that you can control everything in your tests.

If you were to instantiate objects directly in each class, providing dummy data for testing (aka mocking) would be hard if not impossible. You would need to change every single constructor call in every class you want to test. Instead, you can create all of the dependencies in one place. Then if you need to test something, you know where and what to change – only one class which constructs all of the dependencies.

This is a simple but at the same time effective form of dependency injection. Feel free to use a framework like Dagger2 for really complex projects.

// Finally a singleton which doesn't need anything passed to the constructor
object InjectorUtils {

    // This will be called from QuotesActivity
    fun provideQuotesViewModelFactory(): QuotesViewModelFactory {
        // ViewModelFactory needs a repository, which in turn needs a DAO from a database
        // The whole dependency tree is constructed right here, in one place
        val quoteRepository = QuoteRepository.getInstance(FakeDatabase.getInstance().quoteDao)
        return QuotesViewModelFactory(quoteRepository)
    }
}

Finishing up with the view

By now you have already created everything except for the view with which the user can interact. It may not seem like it in this simple project, but normally, your views will seem tiny compared to the other classes. That’s because views in MVVM handle only the least possible amount of data which they immediately pass to / get from the ViewModel.

class QuotesActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_quotes)
        initializeUi()
    }

    private fun initializeUi() {
        // Get the QuotesViewModelFactory with all of it's dependencies constructed
        val factory = InjectorUtils.provideQuotesViewModelFactory()
        // Use ViewModelProviders class to create / get already created QuotesViewModel
        // for this view (activity)
        val viewModel = ViewModelProviders.of(this, factory)
                .get(QuotesViewModel::class.java)

        // Observing LiveData from the QuotesViewModel which in turn observes
        // LiveData from the repository, which observes LiveData from the DAO ☺
        viewModel.getQuotes().observe(this, Observer { quotes ->
            val stringBuilder = StringBuilder()
            quotes.forEach { quote ->
                stringBuilder.append("$quote\n\n")
            }
            textView_quotes.text = stringBuilder.toString()
        })

        // When button is clicked, instantiate a Quote and add it to DB through the ViewModel
        button_add_quote.setOnClickListener {
            val quote = Quote(editText_quote.text.toString(), editText_author.text.toString())
            viewModel.addQuote(quote)
            editText_quote.setText("")
            editText_author.setText("")
        }
    }

}

Conclusion

This is it! You just created your very first MVVM Android app in Kotlin. Let all the new information digest for a while and come back for more app tutorials. In the near future you will also learn how to create fully-fledged apps using MVVM architecture.  Those apps will include a real database ?

 

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • getting error following errors
    1. ForEach statement in QuoteActivity: Only safe or non-null asserted calls are allowed on a nullable receiver of type list
    2. FakeDatabase : @Volatile private var instance: FakeDatabase? = null: Platform Declaration Clash

    • Hello! For the first problem, try changing “.” to “?.” for the forEach.
      As for the second declaration clash, I’m actually wondering why I didn’t get it ? Change “instance” to “_instance”. There is a clash with the automatically generated getInstance() for the property and getInstance() which we’ve created ourselves.

  • Great tutorial.

    By the way, how do you test it? I mean mocking livedata and viewmodel and test it for unit test or instrumented test.

    Thank you

  • Hi
    Nice tutorial on MVVM ,

    I have a proleme with ViewModelProviders.of it give me an error Unresolved reference , also ViewModelProviders it no longer available

  • Love the simplicity of this example. I really appreciate how cleanly you organized this to keep complexity minimized.

    This post was extremely useful to me and worked well. Kudos to the author for a well-written example and explanation.

  • Under the step “Finishing up with the new view” this line does not compile:
    val viewModel = ViewModelProviders.of(this, factory)
    .get(QuotesViewModel::class.java)

    Looks like ViewModelProviders.of(…) expects a Fragment or FragmentActivity, not an Activity for the first argument

    Here is the documentation fro androidx.fragment.app.FragmentActivity:
    https://developer.android.com/reference/androidx/lifecycle/ViewModelProviders

    Is there a quick fix or do I have to create a Fragment?

  • thank you, good tutorial,
    but why you access the data from the viewmodel instead of repository ? the repository here is useless

    • It is useless here, but this is only a crash course. In real apps, you’d perform some caching and data validation maybe, which would happen inside the repository. The ViewModel would then operate with “finished” data.

  • already added the dependecy but still it says unresolved reference: mutableLiveData. Anyone? any help would be greatly appreciated, thx.

  • “textView_quotes.text = stringBuilder.toString()”
    This part, I’m stucked..
    why doesn’t ‘textView_quotes’ appear?

  • I encounter an unsresolved reference issue in the QuotesActivity when I reference the textView or editText, any idea why? I get unrseolved reference and variable expected

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >