Simple MVVM implementation in Android app development
Grzegorz Kwaśniewski
Quite recently, with Architecture Components, Google has nominated Model-View-ViewModel (MVVM) as the core design pattern in Android app development. MVVM is not completely new, as the pattern itself has been functioning for some time now, for example on the .NET platform. It so happens that Microsoft’s engineers are its creators.
Table of contents
- Structure of the Android app development project
- MainView
- Android support
- View only the visible
– Coding time - The viewModel object
- Subscribe to see changes
- The memory cleanup
- MainViewModel implementation
- LiveData objects
- The implementation
- View basket objects and your models
- Implementing the getCoinsData()
- API Webservice
- Unit tests
- Summary
They say Android MVVM is a remedy for overloaded controllers in an MVC-based architecture. In my humble opinion, the pushing of the whole logic into one file is not the fault of the pattern itself, but rather of the wrong interpretation by the programmers. However, putting the discussion aside, MVVM is admittedly a very interesting alternative. It does not solve all the problems and, like MVC, it can be used the wrong way. As with everything, you will need to use your common sense here by choosing MVVM vs MVC.
AndroidThey say MVVM is a remedy for overloaded controllers in an MVC-based architecture. In my humble opinion, the pushing of the whole logic into one file is not the fault of the pattern itself, but rather of the wrong interpretation by the programmers. However, putting the discussion aside, MVVM is admittedly a very interesting alternative. It does not solve all the problems and, like MVC, it can be used the wrong way. As with everything, you will need to use your common sense here.
In this post, I focus on the practical presentation of MVVM architecture. I present a step-by-step process, implementing an MVVM Android architecture in a simple application development.
Let’s start by downloading the starting project here.
You can compile the project, but at the moment it will not do anything interesting.
Structure of the Android app development project
For easy access to individual components, the application structure should be created based on individual views.
According to the Android MVVM separation of concerns, we should create separate packages for models, views and view models. In an additional folder, we will store web services for our view. Thanks to this separation we will make sure that the web service will not contain too many lines of code, which will help not only to understand it more easily but also make it easier to write unit tests.
MainView
We will begin the implementation with the main view. Open a file called MainActivity. There are already two fields – disposable (responsible for cleaning resources used by Rx objects) and linearLayoutManager, which will be used to configure the recycling view. Functions in the object are mostly empty, and we will add their implementation in a moment. For now, let’s focus on the view model object itself.
Android support
In case of platforms such as iOS, we need to rely entirely on our own appliance of the MVVM pattern. Apple does not provide us with any libraries or a system support. The situation is a bit different for Android app development, for which Google has prepared a system-supported implementation. This has several important advantages, such as linking the view model object to the life cycle of the activity, which we will talk about a little bit later.
View only the visible
Before we move on, there is a small issue of dependency injection, testing and view layer. The approach that I personally apply assumes that objects placed in the view layer should be completely stripped off of the business logic. Logic should be moved entirely to the view model object. Views should be responsible only for the presentation of the UI and handling the interaction with the user of the app.
Such an approach allows to maintain proper separation between particular objects, and as a consequence, facilitates writing adequate unit tests. If we assume that the view object will not be covered with unit tests, we can omit dependency injection for the view model object. Going a step further, for a small projects, we can skip using DI frameworks (such as Dagger), in favor of a simpler solution which is pure dependency injection. You can read more about this solution in the section devoted to the appliance of the model view itself.
Coding time
Time to write some code. Add the following below the disposable variable:
private val viewModel by lazy {
ViewModelProviders.of(this).get(MainViewModel::class.java)
}
Using the ViewModelProviders object we create a new instance of the MainViewModel object. The code will be executed in the lazy block, thanks to which we will relieve MainActivity during initialization. The viewModel object will only be created when it is used for the first time. Once created, it will be assigned to the variable permanently, and it will not be possible to overwrite it. Therefore, viewModel can be defined as constant (val).
The viewModel object
One very important remark. The viewModel object created in this way will be kept in memory until the end of the view’s life cycle, in this case, the main activity life cycle. If you change the screen configuration (eg. when the screen rotates), the original instance of the viewModel object will be preserved. This means that we no longer need to use data transfer, for example with Bundle in onSaveInstanceState(). All models (i.e. our data) will be resistant to changes in the life cycle of a given view. As soon as the view is active again, the UI will be filled with appropriate data.
Moving on. In onCreate() call the bindUIData() method, which is currently empty. Put the following code in it:
viewModel.coins.subscribe(this, ::showAllCoins)
viewModel.progress.subscribe(this, ::updateProgress)
viewModel.errors.subscribe(this, ::showErrorMessage)
Subscribe to see changes
Using the subscribe function we subscribe to “listen” to changes in data models stored in the viewModel object. We will talk more about the models when we will get to the implementation of the MainViewModel object itself. The subscribe function is a clever extension that allows us to subscribe in a more user-friendly way than a standard system solution. We have to pass two parameters to the function: the lifecycle owner and a reference to the function that will be called in case of updating a given model stored by the viewmodel. Implementations of individual functions are obvious so we will not focus on them.
We still need to fill the bindUIGestures() function, by means of which we will assign an appropriate action for the button that will start a download process. Here, we will use two tools – Kotlin Android Extensions and RxBinding.
The first one will allow us to quickly link the view from the layout (without the need to call findViewById()), while the second one will let us assign an appropriate action (in the Rx style) to the button. Now add the following code inside the bindUIGestures() function:
disposable = downloadButton.clicks()
.observeOnMainThread()
.subscribe {
viewModel.getCoinsData()
}
The memory cleanup
Due to the fact that “clicking” on the button will be treated as a standard Rx subscription, it will be necessary to assign it to a disposable object, which at the right moment will remove unnecessary resources from the device’s memory. In this particular case, it will happen at the moment of calling the onPause() method of the MainActivity object.
Interaction of the user with the button will call getCoinsData() method responsible for downloading data from the server (we will look at it in a moment ). Your attention may have been drawn to the fact that we are using the downloadButton object, although in fact, its initialization is not visible anywhere. This trick is possible thanks to Kotlin Android Extensions (KTX), which allow us to reduce the boilerplate code and focus on the really important things. In this particular case, we only need to specify the id, which has been assigned to the view in our layout, and Android Studio will “magically” add the appropriate import at the top:
import kotlinx.android.synthetic.main.activity_main.*
An asterisk at the end of the page informs that we want to access all the views in the layout. KTX can do much more, and you can read about it in the official documentation. If you want to check how extensions work underneath, I suggest you compile the project into Java bytecode and then decompile it into Java. The result may surprise you 😉
Only a quick mention of the observeOnMainThread() method. Due to the fact that we will work with UI elements, we have to be sure that all events will be delivered on the main thread of the application. The observeOnMainThread() method is only an extension (syntactic sugar), which will ensure that we can observe subscriptions on the right thread.
MainViewModel implementation
The MainViewModel file is completely empty because this time the whole implementation will be done from scratch. We will start from creating a constructor in which we will “inject” our dependency in the form of ApiService, which is based on ApiServiceInterface abstraction (the name is not very accurate, but it’s just for presenting the concept itself). Now insert the following code into the MainViewModel file:
class MainViewModel(
private val apiService: ApiServiceInterface = ApiService()
): ViewModel() {
}
This way of injecting dependencies is referred to as Pure Dependency Injection because we do not use any additional libraries here. The dependency injection combined with abstraction in the form of an interface will prove very useful when writing tests (we will get there as well).
As we will be using the Android architecture built-in solution, the MainViewModel object must inherit from the ViewModel object included in the androidx.lifecycle package. Thanks to this, our view model will be equipped with options such as the ability to observe the life cycle of the object to which it has been assigned.
LiveData objects
Before we move on, we have to say a few words about the LiveData. LiveData objects are another (next to ViewModel) solution provided by Google, which helps to organize the architecture in the Android system. LiveData stores data models and other objects (such as Activities) can subscribe to listen to the changes taking place in a given model. This is what we did in MainActivity by calling the subscribe function.
LiveData is aware of the current state of the object (it is “life cycle aware”), for which it provides the data. Therefore, the update of the UI elements will not be performed, for example, when the activity is not visible on the screen. A given component will always get the most current data after waking up. This is why LiveData fits so well with ViewModel.
The implementation
Now let’s go back to the implementation itself. Add the following code just under the constructor:
val coins: LiveData<List<CoinModel>>
get() = coinsData
val progress: LiveData<Boolean>
get() = progressData
val errors: LiveData<ErrorMessage>
get() = errorsData
val coinsCount: Int
get() = coinsData.value?.count() ?: 0
private val coinsData = MutableLiveData<List<CoinModel>>()
private val progressData = MutableLiveData<Boolean>(false)
private val errorsData = MutableLiveData<ErrorMessage>()
View basket objects and your models
Here we use protection in the form of public, constant LiveData objects, which will provide a private, mutual equivalent underneath. Such a solution will give us the certainty that a component using LiveData will not be able to accidentally update our models. In the MVVM architecture, the objects in the View basket should not manipulate the data models. This is what the ViewModel object is designed for.
coinsData will be responsible for storing data about current crypto-currency quotations, progressData will help us to determine whether to show the progress view, while errorsData will store a custom object containing information about errors that could potentially occur during connection to the remote server.
Implementing the getCoinsData()
Now we can implement the getCoinsData(). Inside the function, insert the following code:
posable?.dispose()
disposable = apiService.getAllCoins()
.subscribeOnIOThread()
.observeOnMainThread()
.withProgress(progressData)
.showErrorMessages(errorsData)
.subscribe {
coinsData.value = it
}
First, we make sure that our disposable has been cleared of all unnecessary resources, and then we assign a new subscription, which underneath uses the standard solution offered by Retrofit.
subscribeOnIOThread(), similarly to observeOnMainThread() (which we have already seen in MainAcitivty), is a simple extension that adds some beauty to the standard subscription used when working with Rx subscripts.
withProgress(progressData:) and showErrorMessages(errorsData:) are a clever combination of RxJava and LiveData. In the case of the first one, we create a custom Observable object, which will update the LiveData object with the true value at the moment of creating a subscription (i.e. at the moment when we start downloading data from the server). If the subscription is finished, the LiveData object will receive a false value, which means that the progress indicator can be hidden.
showErrorMessages(errorsData:) works in a similar way, with the only difference that it will update the LivaData object with an error that will cause the subscription to be terminated. It will be passed to the main view and eventually displayed on the screen using a standard snack bar. To simplify the example, we only support the basic version of the bug.
The final step is, of course, to execute the correct subscription and then update the coinData object with a new value.
API Webservice
Before we go to the exemplary tests, let’s take a quick look at the implementation of the ApiService object. It was based on an abstraction, which will be useful when we start writing the unit tests (it will be easy to insert a mocked object into the view model object). In the constructor, we once more use dependency injection, but this time we inject the URL, which indicates the place of resources on the server. We will be able to easily enter the address of the local server, which will send us rigidly defined data for test. The implementation of getAllCoins() is a standard solution offered by Retrofit.
Unit tests
What left is to write a few simple unit tests for the view-model and the api service object. Thanks to keeping the proper separation of individual components, we will not have to use Android dependencies during testing. To increase the readability of the example, all tests were placed in the same file. In the production version of the app, these tests should be placed in separate classes.
We will start by creating a simple mock for the ApiServcie object. Insert the following code inside the ApiServiceMock class:
override fun getAllCoins(): Observable<List<CoinModel>> {
val coinModel1 = CoinModel(
"",
"",
"",
"",
"",
"",
"Bitcoin test."
)
val coinModel2 = CoinModel(
"",
"",
"",
"",
"",
"",
"Ethereum test."
)
val coins = mutableListOf(coinModel1, coinModel2)
return Observable.just(coins)
}
We overwrite the single function that was placed in the ApiServiceInterface interface. The function will return rigidly defined data that will be used for examining the MainViewModel object.
Open ExampleUnitTest file and add the following code at the top of the object:
@get:Rule
val rule = InstantTaskExecutorRule()
The above JUnit rule is included in the package ‘android.arch.core:core-testing:1.1.1.1’. You will need it when testing LiveData objects. There are two ways to update the data in the LiveData object. The first one is setValue(), which provides data immediately to active observers, using the main application thread. The second in the form of postValue() provides only the main thread with the relevant task, telling it that the data is waiting to be updated. Using the InstantTaskExecutorRule rule, we will make sure that all data will be delivered synchronously, which will make it easier for us to perform tests.
Now insert the code just below the rule definition:
private val viewModel = MainViewModel(ApiServiceMock())
private val apiService = ApiService("http://localhost:1234/androidmvvmdemo/v1/ticker/")
private val testObserver = TestObserver<List<CoinModel>>()
viewModel is the instance of the MainViewModel object, into which the ApiServiceMock is injected. Thanks to this, there will be no need to establish a connection to a real server in order to get the data needed for testing.
apiService is the instance of the ApiService object to which the test server URL was injected. Such a server can be written, for example, using Node.js. You can also use ready-made and easy to implement solutions, such as Wiremock. It is only important to run the server locally so that when testing the ApiService object there is no need to rely on the Internet connection status. Such a server can also be easily integrated with chosen Continuous Integration solution.
testObserver is an instance of the TestObserver object, which is used to test Rx subscriptions. At the very bottom of the ExampleUnitTest class, there is a static object without which an error “Method getMainLooper in android.os.Looper not mocked” would have been triggered during testing. This is because we use AndroidSchedulers.mainThread() when creating an Rx subscription in the view-model object, which returns the LooperScheduler instance to us. This object uses Android dependencies, unavailable during tests. I found this solution in this answer on StackOverflow.
All we have left to do is to add a few sample tests. Insert the code below under the testObserver definition:
@Test
fun viewModelTest() {
val initialCount = viewModel.coinsCount
viewModel.getCoinsData()
assertTrue(viewModel.coinsCount > initialCount)
assertFalse(viewModel.progress.value!!)
assertEquals(viewModel.errors.value?.getMessage(), null)
}
@Test
fun apiTest() {
apiService.getAllCoins()
.subscribe {
assertTrue(it.count() > 0)
}
}
@Test
fun observableApiTest() {
apiService.getAllCoins()
.subscribe(testObserver)
testObserver.assertSubscribed()
.assertComplete()
.assertNoErrors()
}
Using the viewModelTest() test, we check whether calling the getCoinsData() function will trigger a correct update of the data stored in the LiveData objects. It is worth mentioning that MainViewModel will use data provided by the mocked ApiService object.
The apiTest() test will verify whether the ApiService object correctly retrieves data, while observableApiTest() will check whether the subscription will be executed as it should. Both tests will not perform correctly if you don’t have a local testing server running.
The above tests are only a very small sample of how you can approach the testing of individual components included in the MVVM template. However, they are sufficient to illustrate the basic concept, which can be extended to more detailed cases.
Summary
With the help of this slightly long blog post, I showed you one of the possible solutions that can be used during the implementation of the MVVM pattern in the Android app development. It will work very well in small projects, where we do not want to use extensive external libraries, but at the same time, we want to ensure the possibility of writing unit tests. In the case of larger projects, this solution will require certain addition, like for example data binding.
You can download finished project here.