MVVM and DataBinding: Android Design Patterns

This article describes the MVVM Design Pattern and its components, data binding, and other design patterns and architectural concepts for the Android platform. By Matei Suica.

4.5 (47) · 1 Review

Save for later
Share

This article describes the theory behind the Model–View–ViewModel (MVVM) design pattern and its components. If you want to experiment with it hands-on, open an old project with spaghetti code. If you don’t have one, you can get one from GitHub. Then, try to use the MVVM components introduced in this article.

In some ways, software development hasn’t changed much in the last decade. The basic challenges remain the same:

  • Writing clean code.
  • Having a flexible architecture.
  • Testing the code.
  • Creating a great user experience.
  • Developing features faster than product management can think of them. :]

All these problems had a solution by the time Android entered the market. Desktop and web application developers already had a knowledge base for writing user interfaces. Those solutions worked for Android too, but they were far from ideal.

Android was initially based on Java and apps were typically implemented using MVC (Model View Controller). Ten years later, there’s Kotlin…and there’s MVVM. It was a long journey over those ten years, so let’s catch up!

Note: This article assumes you’re familiar with the basics of Android and Kotlin. If you’re new to Android Development, check out our Android and Kotlin for Beginners series. Need to catch up on Kotlin? Take a look at Kotlin For Android: An Introduction

Getting Started

Understanding Design Patterns

Design patterns represent reusable architectural building blocks within a program. Good building blocks in your code will:

  • Lead to clean code.
  • Resolve common problems using common paradigms, making it easier for multiple developers to work on the same project.
  • Be robust and well tested.
  • Be easier to understand than free-form code.

Imagine entering a bathroom. At a glance, you can identify a toilet. You know what it does and how to use it. You then recognize the sink. Hopefully, you don’t need to poke it and test what it does. The pattern of a bath tub is something you already know as well.

Not all sinks are the same. For example, in the UK, you might find a sink with separate pipes for cold water and hot water. While it’s unusual for the rest of the world, you’d still know how to use the sink.

Design paradigms: UK sinks vs the world

With design patterns, developers can apply known solutions to existing problems.

Using Architectural Design Patterns

The concept of design patterns appeared in a famous book by the same name, which described three categories for these patterns:

  • Creational
  • Structural
  • Behavioral

A fourth category, Architectural design patterns, helps developers follow an architectural style. They help you separate different components within a project.

In the Enterprise world, one of the most popular design patterns is the “three-tier” or “three layers” pattern. Another useful one is the “client-server” model. Mobile apps also benefit from many of these patterns, but some work better than others.

MVVM is an architectural design pattern that works well for mobile apps.

Common Architectural Design Patterns in Android

MVC and MVP

Model-View-Controller (MVC) and Model-View-Presenter (MVP) are two design patterns that are very similar and have much in common with MVVM. Google didn’t push for one single design pattern for Android in the beginning. However, the documentation uses MVC more often than not. This pattern has three components: Model, View, and Controller.

In MVC, communication between components looks like a triangle:

  • The View displays the state of the Model,
  • The Controller handles the View input.
  • The Model stores the result that comes out of the Controller.

MVC flow diagram

MVC can be hard to test because of this communication model. Navigating the code can also be tedious. The business logic should split between the Controller and the Model, but sometimes the logic leaks into the View.

This creates a kind of coupling that has a spiral effect. In the end, it encourages Android developers to put the bulk of the code in an Activity or Fragment class. The problem has a nickname: “massive view controller”. Ironically, this problem has the same initials as MVC. But it makes the app hard to maintain and nearly impossible to test.

Despite this, the pattern is still good enough for small projects. But there are better patterns available.

Note: Have you ever seen an Activity or a Fragment that exceeds 1000 lines of code? That’s a Massive View Controller! It’s that file that always generates merge conflicts. It has 0% test-coverage, and everyone avoids changing it.

For example, MVP shifts the “controller” in MVC between the Model and the View, naming it a Presenter. This helps concentrate the business logic in the Presenter. On the flip side, the Presenter is also responsible for adapting the data in the model for display in the View. For this reason, the Presenter is sometimes called a “supervising controller”.

The difference between MVC and MVP is not huge, but decoupling the Model from the View has advantages. It allows for more flexibility and offers easier UI and Unit Testing.

MVP flow diagram

The problem is that the Presenter is still coupled with both the Model and the View. Time to take another step down the decoupling road!

Taking the Next Step: MVVM

Compared to MVP, MVVM decouples the View from the ViewModel by using something called a Binder. The ViewModel doesn’t know about the View or Views subscribed for changes. The Binder delivers the changes from the ViewModel to the View. This allows developing and testing the View in isolation without breaking the ViewModel.

Think about it: should your UI influence the business logic? You’re right, it shouldn’t!

This pattern isn’t perfect either, but it’s very good. So, let’s dig into it.

Understanding MVVM Components

MVVM is now one of the most loved patterns out there, as it has had plenty of time to mature. The web community adopted the pattern after the Microsoft team formalized it in 2005. It eventually made its way into every UI-based framework.

One key advantage of MVVM is that it offers the right amount of decoupling. Another good thing is that the learning curve is similar to the other patterns.

MVVM has three main components: Model, View, and ViewModel.

MVVM flow diagram

Unlike MVP and MVC, there’s a fourth component: The Binder. This is the mechanism that links the Views to the ViewModels.

The Binder is usually handled by the platform or a third party library, so the developer doesn’t have to write it. For Android, you have the DataBinding library at your disposal.

Defining the Role of the View

In MVVM, the View is very lightweight because it has almost no logic in it. The View is everything UI related. In Android, this is usually an Activity or a Fragment. Its role is to display whatever it receives from the ViewModel and forward input to it. It’s as simple as that!

Here’s an example View in an Android class named MainFragment:

class MainFragment : Fragment() {

  companion object {
    fun newInstance() = MainFragment()
  }

  private var selectedUser: User? = null
  private lateinit var viewModelBasic: BasicMainViewModel

  override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View {
    return inflater.inflate(R.layout.main_fragment, container, false)
  }

  override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    viewModelBasic = BasicMainViewModel()
    viewModelBasic.uiModel.observe(this, Observer<UiModel>{ uiModel ->
      // update UI
    })
  }

  fun onDeleteButtonClicked(v: View) {
    viewModelBasic.deleteUser(selectedUser)
  }
}

In onActivityCreated(), a ViewModel object is instantiated from the class BasicMainViewModel. In the onDeleteButtonClicked() handler, you see a typical interaction between View and ViewModel: The fragment passes a UI event, the user clicking the delete button, off to the ViewModel.

Defining the Model

The Model is a lightweight component that doesn’t know about any other component. Its role is to store the state of the system and let the ViewModel query it.

The Model is often created using a Repository pattern. Data Access Objects (DAO) that provide Create-Read-Update-Delete (CRUD) operations can help the repository fulfill requests. For Android, the Google team released the Room Persistence library within Jetpack for this purpose.

A simple Model interface could look like this:

interface Repository {

  fun insert(user: User)

  fun getAll(): LiveData<List<User>>

  fun delete(user: User)

  fun update(user: User)
}

The interface provides Create (insert()), Read (getAll()), Update (update()) and Delete (delete()) operations on th Model.

You will see an example implementation with Room later in the article.

Finding the ViewModel’s Place in MVVM

The ViewModel is the centerpiece of MVVM. It has all the business logic and some of the display logic in it.

The ViewModel needs to adapt the information in the Model for the Views to display. The ViewModel also transforms input events into data for the Model to store. If something goes wrong, look into the ViewModel first!

Here’s what BasicMainViewModel might look like:

class BasicMainViewModel(private val repository: Repository) : ViewModel() {
  val uiModel: MutableLiveData<UiModel> by lazy {
    MutableLiveData< UiModel >()
  }

  fun generateReport (){
    // TODO Add complicated report computation
  }

  fun deleteUser(user: User?) {
    deleteUserFromRepository(user)
    uiModel.value?.userList?.remove(user)
  }

  fun deleteUserFromRepository(user: User?) {
    if(user != null) { repository.delete(user) }
  }
}

The ViewModel here takes a Repository in its constructor so that it can communicate with the Model. And it provides functions that calculate data for display (generateReport()) and allow deletion of Model data.

Improving Testability

Writing testable code is one trait of a good developer. After reading this article, that’ll be you. :]

Hiding Behind Interfaces and Dependency Injection

The easiest thing you can do with your code to improve testability is to hide it behind an interface. This means that your class will implement an interface that exposes only the public methods. The public methods form a type of Application Programming Interface (API).

You’ll need to think twice about what you expose for your API. Once something is in the API, other parts of the code will use it. The API is like a contract that methods can’t change. The advantage is that you can change the implementation without breaking anything.

For example the class BasicMainViewModel should hide behind an interface like this:

interface MainViewModel {
  fun generateReport ()
  fun deleteUser(user: User?)
  fun deleteUserFromRepository(user: User?)
}

Note: Have you noticed the Repository in the constructor? That’s an interface too. This is an example of “dependency injection”: Allowing other objects to “inject” a Repository in the ViewModel.

You’ll need to create interfaces for all components of the MVVM, not just the ViewModel.

But how can you prevent users of your code from using the implementation? There are a couple of ways to create this constraint.

One method is to make the implementation class internal. Then, provide a public Factory that always returns a reference to the Interface type. This way, the implementation never leaves the module.

Consider this BasicLocationProvider that might be used within a ViewModel:

class BasicLocationProvider: LocationProvider {
  val location: Location? = null

  fun refreshLocation() {
    // TODO trigger a location refresh in Android
  }
}

This code stores and exposes a location, and includes a method to refresh that location. You need to protect it from pesky developers who will use the implementation all over the code. First, declare the interface:

interface LocationProvider {
  fun refreshLocation()
  val location: Location?
}

Then, make sure that the class implements the interface and declare it internal:

internal class BasicLocationProvider: LocationProvider {
  override val location: Location?
    get() = null

  override fun refreshLocation() {
    // TODO trigger a location refresh in Android
  }
}

Lastly, create a Factory object that offers a LocationProvider to anyone interested:

object LocationProviderFactory {
  val locationProvider: LocationProvider =
    BasicLocationProvider()
}

With these modifications, the implementation of BasicLocationProvider is well-protected from users of the class.

Most Dependency Injection frameworks have an easier way of doing this. For example, in Dagger, you can expose the @Module, but keep the implementation classes internal.

When it comes to testing, constructors will receive mock objects that implement the interfaces. This makes testing the code easier. Testing frameworks and libraries let you easily create mock objects in order to control the input.

Testing Each Component

Now take a moment to review how you test each of the components covered so far.

  • View: This is the simplest component in MVVM. It doesn’t know about any other component and it doesn’t control anything. Testing the View is also simple. First, provide some controlled data just like a ViewModel would. Then, check the visual results. Voilà!
  • Model: This is usually simple to test also. The Model has no knowledge about the external world so you have complete control over it. All you have to do is to call “execute” methods on a Model object and then check the results with query method calls.
  • ViewModel: Testing this component is a little tricky. You have to mock the Model and call methods on the ViewModel. After that, check that the data available for the View is correct. You can also check that the methods called on the mock Model are correct. If the ViewModel depends on other components, you need to mock those as well. Still, testing the ViewModel is easier than testing the components of any of the previously mentioned design patterns.

Best practices in Testing MVVM

Testing is straightforward when you apply a good pattern. There are a few secrets to testing MVVM, but here’s the best approach:

  • Start testing the ViewModel. Test each one of the public methods, going through as many cases as you can. Not just the obvious paths, but real edge cases that you’re sure are never going to happen. Trust me: users and future developers will reach those edge cases. :]
  • If something isn’t testable, wrap it! Create an interface that contains all of the methods you need from the untestable class. Create a class that implements that interface and instantiate the untestable class you wanted to use inside of it. Then, inside your class, call the methods from the untestable class. This is called the Proxy design pattern.

Now you have an interface you can use in your app that’s easily replaceable with a mock object. This way, you can “throw” untestable dependencies outside your class.

You should keep Android SDK framework components and external libraries at arms-length. By wrapping them this way, you make sure you don’t spread dependencies all over the code. You use the interface you’ve created and keep the frameworks and libraries in a single place.

Keep your tests short and make sure they are repeatable.

It’s not your responsibility to test the framework. You can’t control the framework, so there’s no point in testing it. If your Model only forwards methods to another library like Room, there’s not much to do in terms of testing. You can add tests once your Model needs to do something else, like filtering or computing.

Test your View with different device configurations. Mock the ViewModel to generate all the states that a View could receive.

Adding Android’s Architecture Components

So far, you’ve seen the main MVVM components and discussed testing them. But what about the Binder layer? The Android SDK provides Data Binding to implement a Binder.

Using DataBinding

Data binding is a technique that connects data sources with consumers and keeps them in sync. Android’s Data Binding Library lets the developer keep the UI in the layout XML files and generates code from them. The library also does the heavy lifting of synchronizing the View with data that comes from the ViewModel.

To add data binding to your project, you need to add the following to your app’s build.gradle file:

android {
  ...
  dataBinding {
    enabled = true
  }
}

Now, your layout XMLs will need to have the following structure:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>
    <ConstraintLayout... /> <!-- UI layout's root element -->
</layout>

To transform XML from the default structure to a data binding one, you need to wrap the root layout, in this case a ConstraintLayout, inside a layout tag. Then, at the top of the layout, just inside the layout tag, you will need to add a data tag, containing the objects that are used for binding.

Note: Once you have data binding enabled and sync your project, Android Studio offers a shortcut for transforming any layout into a data binding one. Select the root element of your layout and press option + return. A pop-up appears with the option “Convert to data binding layout”. Select that and the layout will add the data and layout tags automatically.

The UI will reflect every change in the ViewModel via the variables inside the data tag. In the example, there is a variable named user.

You have to tell the UI elements what property they need. For example:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>
</xml>

Here there are two text views that are bound to the user.firstName and user.lastName values.

You will still need to add different listeners to your UI and forward those events to the ViewModel. Lucky for you, the DataBinding library can also help with that. The library generates a binding object for accessing the UI elements:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater)
}

In the above, you create a binding value using the generated binding object.

Adding Android’s own ViewModel

Creating your own ViewModel class is great, but Android Jetpack offers more. Jetpack’s ViewModel has its own lifecycle, managed by the library. This means that the library will keep the ViewModel when the OS recreates the Activity. You also don’t need to synchronize the UI state.

To begin, create a class that inherits from Jetpack’s ViewModel:

class ProfileViewModel : ViewModel() {
  val uiModel: MutableLiveData<UiModel> by lazy {
    MutableLiveData< UiModel >()
  }
}

Then, in your Activity or Fragment, add this:

val model = ViewModelProviders.of(this).get(ProfileViewModel::class.java)

If the activity is re-created, it receives the same ProfileViewModel instance as before. When the owner activity finishes, the framework calls the ViewModel object’s onCleared() method. That’s when the ViewModel is really destroyed.

Loose Coupling with LiveData

Jetpack also provides the LiveData class. LiveData is just the Observer pattern improved with lifecycle awareness. Most of the problems in Android development come from lifecycle handling. Developers that use LiveData are less likely to have:

  • Memory leaks
  • Crashes
  • Outdated UI
  • To use LiveData, first create your LiveData object in your ViewModel:

class ProfileViewModel : ViewModel() {
  val uiModel: MutableLiveData<UiModel> by lazy {
    MutableLiveData< UiModel >()
  }
}

After that, connect your View to this LiveData. In your Activity or Fragment, after you’ve created the ViewModel, add this:

viewModel.uiModel.observe(this, Observer<UiModel>{ uiModel ->
  // update UI
})

The Database in the Room

The database we discussed earlier named Room is also a Jetpack component. Using Room in your Android app is straightforward. There are three types of classes that you need to write:

  • Entities
  • Data access objects
  • Databases

Entities

Entities are POJOs (Plain-Old-Java-Objects) with annotations for column names and a primary key:

@Entity
data class User(
  @PrimaryKey var uid: Int,
  @ColumnInfo(name = "first_name") var firstName: String?,
  @ColumnInfo(name = "last_name") var lastName: String?
)

Data Access Objects

Usually, each entity has its own data access object (DAO). The DAO implements the CRUD operations, as well as other helpful methods.

@Dao
interface UserDao {
  // Create
  @Insert
  fun insert(user: User)

  // Read
  @Query("SELECT * FROM user")
  fun getAll(): List<User>

  // Update
  @Update
  fun update(user: User)

  // Delete
  @Delete
  fun delete(user: User)
}

Here, UserDao, has been given each of the CRUD operations tagged with the corresponding annotation.

The best thing about Jetpack components is that they work great together. For example, Room can also return LiveData objects.

// Read
@Query("SELECT * FROM user")
fun getAll(): LiveData<List<User>>

Cool, huh? This method is lifecycle aware and will propagate changes in a reactive way.

Next up for using Room, you need to create the database.

Database

The database interface needs to know what entities it will have. This lets Room generate the appropriate classes and methods.

@Database(entities = arrayOf(User::class), version = 1)
abstract class MvvmDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

Room also handles the versioning of your database. That’s what the second parameter version is for. It will help you run migrations when you upgrade the database.

Lastly, you need to initialize the database when the app starts:

val db = Room.databaseBuilder(
  applicationContext,
  MvvmDatabase::class.java, "mvvm-article"
).build()

You provide the builder a context, the database class, and a name for the database file, e.g. “mvvm-article”.

Searching For More

MVVM is not the end of the story for architectural design patterns. There are a number of other patterns that come in handy on Android, especially as the size of your app grows.

Clean Architecture

In 2012, the concept of a Clean Architecture was developed. Since then, every framework community has tried to include a variation of it.

This concept has four layers:

  • Entities
  • Use cases
  • Interface adapters
  • Frameworks and drivers
    • Clean Architecture separates the code even better than MVVM. That doesn’t mean MVVM is not good, but it is not necessarily as “clean” as it could be. For example, the Model in MVVM can sometimes contain adapters and frameworks. But in Clean Architecture, these are separated out.

      clean architecture by Uncle Bob diagram

      The most important thing to remember in Clean Architecture is The Dependency Rule. This rule says that source code dependencies can only point inwards in the diagram above, towards the center. Nothing in an inner circle can know anything at all about something in an outer circle.

      VIPER

      VIPER is a design pattern that the iOS community developed for their projects. It’s their way of implementing Clean Architecture on iOS.

      VIPER stands for: View, Interactor, Presenter, Entity, Router.

      As you can see, there are some new components that appear in this acronym. It also suggests that VIPER is a descendant of MVP, not MVVM.

      Indeed, you can think of VIPER as MVP with an Interactor and a Router.

      The Interactor handles part of the Presenter’s responsibilities in MVP. The business logic moves into the Interactor, leaving the UI events and display to the Presenter.

      However, this leaves one problem with the View. The View still handles all the routing between modules. That’s where the Router comes in.

      VIPER design pattern flow diagram

      In Android, having a Router to handle navigation was tricky. The developer can’t create Activity instances, because the framework does that. There are workarounds so that a Router can be created with Activity instances too. But Google recently announced Fragment improvements in the Support Library and recommends using them, and Fragments can be instantiated by the developer.

      Combined with a Single Activity app structure, the Router can take its rightful place in the VIPER design pattern for Android. With Fragments, Routers can have full power over navigating through modules.

      Where to Go From Here?

      That’s a lot of information that you’ve covered in this article!

      You now have a good understanding of what design pattens are in software development, with a focus on architectural design patterns.

      You know about the most important patterns out there, and the weaknesses of each one. And the most important thing of all: You now know how to implement MVVM, how to test it, and how to use Android’s Architecture Components from Android Jetpack to get the most out of MVVM.

      Good job keeping up with all these patterns and components!

      To dive deeper into MVVM, you can check out the MVVM on Android video course. You’ll learn more about each of the MVVM’s components and you can practice the design pattern in small challenges.

      If you want to know more about the Google’s components that can help you get the most out of MVVM, read the Android Architecture Component: Getting started tutorial and all the screencasts related to them.

      To learn more about Dependency injection you can watch the Dependency Injection with Koin screencast. If you prefer Dagger2, you can read Dependency Injection in Android with Dagger2 and Kotlin. Or watch the screencasts Getting Started with Dagger and Dagger Network Injection.

      Even though it’s iOS based, the Design Patterns by Tutorials book covers many of the patterns mentioned in this article.

      Finally, design patterns don’t really change much from platform to platform; just the specific code examples and implementation may vary. Be sure to check out the upcoming Advanced App Architecture book for Android for more details on MVP, MVVM, and other architectural patterns on Android.

      If you have any questions or comments, please join the forum discussion below!