State machines – they’ve been around for a long time in computer science, but are perhaps forgotten in the last few decades, at least in mobile apps. My team at Wayfair chose to revisit state machines and give them a try in our production app. This is how we came upon MvRx and contributed to their open source efforts.

About the Waystation App

Before we dive in, a little context about our app. The Waystation App supports internal users at Wayfair to prepare for trade shows. We needed to support an offline mode for the app so that it could function in less-than-ideal internet conditions, which is a common issue at trade shows. We were competing with the most reliable system in known history – pen and paper! To win that race, we needed to have a really solid and stable mobile app. We needed an approach where we could have stateless views that would fully depend on state represented by our entities. 

In order to achieve this, we decided to re-approach our architecture. While looking into different solutions, we ran into the MvRx library from Airbnb. It looked like it could help us achieve what we needed. What we arrived at is a hybrid of MVVM + data binding + MvRx, which warrants its own article to give a greater amount of context. Stay tuned for that another time!

The crucial part was to have ~100% test code coverage of our business logic, which prevents developers from creating more work for our QA team, or worse, shipping a bug. We had a few bumps along this road – here we will explain how we got through them all by contributing to the MvRx codebase.

Let’s start

Thanks to data binding and MvRx, we managed to simplify our fragments by having a ViewBinding layer. 

The responsibility of the ViewBinding layer is to keep a connection between state and view. The ViewBinding layer reacts on every state change and populates the view with the new data, but also reacts to any UI events and propagates them to ViewModel. Based on business logic, ViewModel then triggers a state change. The circle is thus closed; we have a nice, unidirectional data flow as represented in the diagram below:

ViewModel is a layer that holds the state and the only layer which triggers the state change. In our view layer, there is very little logic that we always test. It is mostly the pure and passive representation of the state. Some projects are really strict on this and cause a back-and-forth to ViewModel, which introduces an explosion of callbacks. We decided to be pragmatic and to not go through ViewModel if we didn’t need to.

What about Unit Tests?

The ViewModel layer is context-free, so we didn’t have any issues obtaining 100% test code coverage.

ViewBinding, however, is a tricky one. We had several issues here, but one of the most challenging, up until now, was how to run asynchronous callbacks provided by MvRx. Let’s take a look at the code:

viewModel.selectSubscribe(TaskDetailState::taskDeleted) { taskDeleted ->
   if (taskDeleted) {
       //… change view based on state change
   }
}

If you deep dive into the selectSubscribe function, you will find that if you call it from MvRxView, it will automatically retrieve the lifecycleOwner from the given fragment. So, in order to get into the subscribe block above, fragments need to be in a resume state. This is a really important optimization that enables retrieving and subscribing to ViewModels in a view-lifecycle-aware way. It is similar to LiveData, which is lifecycle-aware observable, but this behavior will never happen in unit tests. There was no way to get into a function block and test the code involving this block.

Our contribution

To alleviate the above problem, our team opened up a GitHub issue for it, seen here: https://github.com/airbnb/MvRx/issues/198. After a short conversation with the Airbnb team, we were able to come up with a solution, seen here: https://github.com/airbnb/MvRx/pull/235.

We built a ToDo sample app where we pushed our architecture out into the wild. This ToDo app follows the specifications in the Google Samples Android Architecture repository. Ours is unique because it is a combination of MvRx and data binding in its core architecture, and utilizes technologies like the Room Database, RxJava, and Dagger. Check it out here. 

With the latest MvRx version, we were able to test the above piece of code and have bumped up our test code coverage. We were able to achieve this by using very simple fragments, context-free ViewModel use, as well as using MvRx with the ViewBinding layer.

We have incredibly high test code coverage in our app, and we’re proud of it! We’re not using tools to mock the Android context, which means that tests are really fast. Because of this, we are very confident in our code and are able to ship quickly and reliably, as well as be able to contribute back to the open source community to help others experiencing the same challenges.