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.
Let’s start with adding a few components to the MvvmApp library:
The source code for this post is tagged as part2 and available on GitHub.
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.
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.
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.
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.
Let’s use the newly added RecyclerView components to convert AndroidVersionsFragment. We’ll update:
And we’ll add:
This is the ItemViewModel for a single AndroidVersion item.
This is the layout file for the AndroidVersionItemViewModel above. We’re binding the selected state and the version and codeName text values.
Now we can update AndroidVersionsAdapter to extend from RecyclerViewAdapter, and use the AndroidVersionItemViewModel we created above.
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
Now the code for this Fragment becomes really simple.
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!
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.
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!
I’d love to hear your thoughts! Please leave your response below, or feel free to just say hi on Twitter @hiBrianLee.
Other parts of this post:
- Writing Testable Android MVVM App: Prelude
- Writing Testable Android MVVM App: Part 1. ViewModel
- Writing Testable Android MVVM App: Part 2. RecyclerView
- Writing Testable Android MVVM App: Part 3. Dagger 2
- Writing Testable Android MVVM App: Part 4. ViewModelTest
- Writing Testable Android MVVM App: Part 5. Espresso
Some of my other posts: