Writing Testable Android MVVM App: Part 2. RecyclerView

In the previous post, I converted MainActivity and ClickCountActivity to use ViewModels. We’ll see how we can apply ViewModels to RecyclerView by converting AndroidVersionsActivity in this post.

Android Versions Activity

Let’s start with adding a few components to the MvvmApp library:

  • ItemViewModel
  • RecyclerViewAdapter
  • RecyclerViewViewModel
  • ViewModelBindings

The source code for this post is tagged as part2 and available on GitHub.

MvvmApp Library

ItemViewModel
Similar to how items were bound to a ViewHolder in a standard RecyclerView, we’ll create a base ItemViewModel for items to be bound to.

RecyclerViewAdapter
This will be the base class for all RecyclerView adapters that use the ItemViewModel. This adapter will just pass the item from onBindViewHolder() to the ItemViewModel.

RecyclerViewViewModel
This ViewModel will serve as a basis for ViewModels containing a single RecyclerView. Its main responsiblities are:

  • binding the RecyclerViewAdapter and LayoutManager to the RecyclerView
  • Saving and restoring the LayoutManager state on configuration change

Normally, RecyclerView will automatically save/restore the LayoutManager state and remember the position of the items. However, this only works if the LayoutManager is set before onRestoreInstanceState() is called on the RecyclerView. Because we are using data binding to set the LayoutManager, this will happen after onRestoreInstanceState(), so we’ll have to do this ourselves. I will probably go into a deeper explanation in a separate post later, but that’s basically the gist of it.

ViewModelBindings
In here, we’re making a custom setter to bind the RecyclerView to the RecyclerViewViewModel. This is what allows us to bind the RecyclerView to the RecyclerViewAdapter and LayoutManager in RecyclerViewViewModel.

Sample App

Let’s use the newly added RecyclerView components to convert AndroidVersionsFragment. We’ll update:

  • AndroidVersionsAdapter
  • AndroidVersionsFragment
  • item_android_versions.xml
  • fragment_android_versions.xml

And we’ll add:

  • AndroidVersionsItemViewModel
  • AndroidVersionsViewModel

AndroidVersionItemViewModel
This is the ItemViewModel for a single AndroidVersion item.

item_android_version.xml
This is the layout file for the AndroidVersionItemViewModel above. We’re binding the selected state and the version and codeName text values.

AndroidVersionsAdapter
Now we can update AndroidVersionsAdapter to extend from RecyclerViewAdapter, and use the AndroidVersionItemViewModel we created above.

AndroidVersionsViewModel
We’ll extend from RecyclerViewViewModel and make it:

  • create the Adapter and LayoutManager
  • set the data for the Adapter
  • remember the selected state of each item on configuration change

AndroidVersionsFragment
Now the code for this Fragment becomes really simple.

fragment_android_versions.xml
This is where the custom binding created in ViewModelBindings will be used to bind the RecyclerView to AndroidVersionsViewModel.

So that covers the RecyclerView for now!

Unit Test!

Now that we’ve converted the Sample App to use MVVM, let’s try some unit tests! We’ll use Mockito in our unit test and verify if the ViewModels are doing what we expect them to do.

MainViewModelTest
Since it’s a simple ViewModel, we’ll start by testing MainViewModel.

Pretty simple and awesome right? You can test button clicks without even having an actual Activity or doing a UI test!

Not so fast…

First of all, this test won’t pass because you’ll run into Intent not Mocked:

java.lang.RuntimeException: Method toString in android.content.Intent not mocked

This is happening because it’s trying to verify that the expected Intent matches with the actual Intent, and it’s calling methods on the Intent object, which is not implemented in the test environment (more info here).

The second problem is that the test has to create an Intent object and pass it in, knowing more implementation detail than it should. The test also has to know the exact URL and have it hardcoded in the test.

So what can we do to make it better? We’ll explore this in the next post, Writing Testable Android MVVM App: Part 3. Dagger 2!