Pragmatic Approach to Android MVVM: Part 1

Brian Lee
5 min readJan 29, 2018

--

Android MVVM: Then and Now

In my previous posts, I explored different ways to apply MVVM to Android app architecture. I wanted to see how far I can push the Android app architecture to use MVVM. By going through the exercise, I hoped to learn what’s possible, expose the process I went through, and share my learning experience.

At the time of my previous posts, we had around 150 unit tests and 270 Espresso tests in Trunk Club Android app. We’ve since incorporated MVVM in the app, and now have close to a thousand unit tests, moving away from Espresso.

Moving to MVVM architecture had several advantages:

  • It separated out Android specific nuances from the core code, making it easier to onboard someone new to Android.
  • Similar MVVM pattern could be applied to iOS, making it easier for Android/iOS developers to work together, and even occasionally code review each other’s code. This promoted cross-platform knowledge sharing.
  • Tests were faster to write and execute, compared to Espresso tests. It took 22 seconds to run 992 tests. It also became much easier to set up tests to execute on a CI platform.
  • Tests were easier to read.

While the previous posts were more of an exploration of how MVVM can be applied to Android, they weren’t meant to be a pragmatic guide on how to write an Android MVVM app. In this post I will share the learnings from using MVVM in a production app. I will focus on the benefits above and show one way of taking a pragmatic approach to Android MVVM app architecture.

The App

Image Analyzer Design

In this post, I will explore what it’s like to write a practical MVVM app for an Image Analyzer. The app will show photos from the device, analyze the selected photo via Google Cloud Vision API, and show labels for the photos, scaling them according to their score.

What does it mean to take a pragmatic and practical approach? With MVVM, the tricky part is to make the right compromise that gives you the most benefits. With this example, we’ll explore how we might separate out the Android components from testable ViewModels, and attempt to strike the appropriate balance.

We’ll cover the following flow and examples:

  1. Permission Request
  2. Loading Photos into RecyclerView
  3. Analyzing Image

Permission Request

Can Ask Permission / Don’t Ask Again / Permission Granted

Plan of Attack

To show photos, we need READ_EXTERNAL_STORAGE permission, which is a dangerous permission requiring user’s consent. The above shows the intended design for each of the following cases:

  1. We don’t have permission yet and we can ask for permission.
  2. We don’t have permission and user has selected “Don’t Ask Again”, which prevents us from opening the permission dialog.
  3. User has granted permission.

To approach this, we’ll do the following:

  1. GalleryActivity will handle Android specific concerns and provide any dependencies to the ViewModel.
  2. GalleryViewModel will take data and represent the View state.
  3. The View will be bound to the GalleryViewModel via Data Binding.
  4. The ViewModel will send any requests back to the Activity through an EventBus, requiring no knowledge of the Android components.

Representing the above logically is pretty straight forward:

  1. Show the permission layout when we don’t have permission yet.
  2. Update the permission text depending on whether we can ask for permission again.
  3. Show the gallery layout when we have permission.
  4. Describe and send any actions through the EventBus.

Layout

In the layout below, the following components are bound to the ViewModel with Data Binding:

  • RecyclerView (Gallery): viewModel.galleryVisibility
  • LinearLayout (Permission): viewModel.permissionVisibility
  • Permission Header Text: viewModel.permissionHeader
  • Permission Request Text: viewModel.permissionRequestText
  • Permission Button Text: viewModel.permissionButtonText
  • Permission Button Action: viewModel.onClickPermission

ViewModel

The ViewModel for this becomes quite simple. We just need to check the PermissionStatus, and return the appropriate values for the bounded elements.

When the permission button is clicked, we just need to fire off an event describing the action we’d like it to result in. Similarly, when we know we have the permission, we can send off an event to start loading the photos.

Activity

The Activity will take care of reading the latest permission status from the Android system, and set the permission value in the ViewModel. It’s best not to fight the Android platform on how it does things, so we’ll let the Activity take care of the actual Android permission request.

The snippet below highlights the relevant part of the Activity handling the permission:

I’ve used a helper method for getPermissionStatus to represent the three different PermissionStatus options, which is below:

As you can see, I did not try to move all of the code into the ViewModel. It’s important to understand what’s best suited for an Activity to handle, and what’s most beneficial to have in a ViewModel.

Although it may be possible to rip out as much code as possible from an Activity, it will eventually go against our original intention of writing an MVVM app in the first place — to have a clean and testable app that we can iterate fast on.

Testing

With the code we have now, we can test what the View will do:

  • Does it show the permission elements when we don’t have a permission?
  • Do each of the text elements show the appropriate text depending on the permission status?
  • Does the button fire off the appropriate action event depending on the permission status?
  • Does it load the photos when we have permission?

With EventBus and Mockito, even the actions that we expect to happen become very testable, as shown in the test code below:

On the next post, we’ll dive into how we can handle loading photos into RecyclerView and apply MVVM to it.

Thank you for reading! Feel free to say hi or share your thoughts on Twitter @hiBrianLee or in the responses below!

Full project source code: https://github.com/hiBrianLee/ImageAnalyzer

Other parts of this post:

Some of my other posts:

--

--