The Model in MVVM

I have often had the feeling it was not clear to me what the Model was in my apps. While applying the Model-View-Presenter pattern, I implemented View interfaces and Presenter classes, but there was no explicit Model in my code.

A while ago, I took a break from Android development and decided to learn React and CycleJS. It took me some time to get my head around these different ways of structuring front-end applications.

A few months later, back in the Android realm, I started looking into the new ViewModel and LiveData classes of the Architecture Components library. I decided to put in practice the concepts I had learnt by using the aforementioned Javascript libraries: I would represent the Model in my Android apps as a State, Events and Side-Effects.

What is the State? The dictionary says it is “the particular condition that (…) something is in at a specific time”. For our purposes, that “something” will be the View.

What is an Event? Again, the dictionary says it is “a thing that happens or takes place”. I would add that, in the context of an application, an Event has the potential to change or modify the State.

According to the dictionary definition, a Side-Effect is “ a secondary, typically undesirable effect of a drug or medical treatment”… Well, I guess the dictionary definition will not help us this time.

A Side-Effect, in the context of our Model, is something that happens as a consequence of an Event. For example, in the Event of a user pressing a button, a Side-Effect could be the execution of a network request or database query. These Side-Effects could produce a result in the form of an Event (e.g.: Success, Error, etc.).

Show me the code!

In a previous article I provided a very basic example on how to represent streams of State and Events in the context of a ViewModel using RxJava.

As a consequence of spending time applying the approach and refining it, I wrote a very small Android library called RxModel. It is so small you may as well copy and paste the code into your project. In this article I will provide an example of how to use it to structure the Model in your apps.

For this example, we are going to build an application consisting of a screen with a TextView and a Toss Button . When the Button is pressed, it becomes disabled and the TextView reads “Tossing…” for a second. After that, the Button is re-enabled and the TextView reads either “Heads” or “Tails”.

I made a GitHub repository with the implementation of the following example, in case you would like to check it out.

Let’s begin by modeling the Events for this use-case:

sealed class CoinTosserEvent {
object Toss : CoinTosserEvent()
object Heads : CoinTosserEvent()
object Tails : CoinTosserEvent()
}

I have used a sealed class to define 3 types of Events. This way I ensure the application is only going to expect a finite set of Event types.

Next, we will represent the State for the same use-case:

data class CoinTosserState(
val isTossing: Boolean = false,
val isHeads: Boolean = false
)

I used a data class to represent the different properties of the State. The State is immutable: you cannot modify its properties. But how are we going to update the State after an Event occurs? We will we make a copy of the State and change only the properties we need to change (data classes count with a very handy copy(...) method that makes this easy).

Which takes us to the following step: defining a Reducer (if you haven’t already, you will need to add RxModel to your project to move forward).

object CoinTosserReducer : Reducer<CoinTosserState, CoinTosserEvent> {
override fun apply(
state: CoinTosserState,
event: CoinTosserEvent
) = when (event) {
CoinTosserEvent.Toss -> state.copy(
isTossing = true
)
CoinTosserEvent.Heads -> state.copy(
isTossing = false,
isHeads = true
)
CoinTosserEvent.Tails -> state.copy(
isTossing = false,
isHeads = false
)
}
}

The Reducer consists of an object with a single function which takes the current State and an incoming Event and returns a new updated State.

So far we expressed what the Model of our app is in a declarative manner. But I purposely introduced a requirement in the specification of the use-case:

When the Button is pressed, it becomes disabled and the TextView reads “Tossing…” for a second.

What the statement above implies is that some work is going to be done in the background for some time and it will eventually produce a result. We are now dealing with asynchrony, which is what RxJava is meant to help us with. Triggering this asynchronous work is what I call a Side-Effect.

Let’s define a concrete Model and declare its Side-Effects:

class CoinTosserModel
: StateEventModel<CoinTosserState, CoinTosserEvent>(
CoinTosserState(),
CoinTosserReducer
) {
private val tossEventsObservable = eventObservable
.ofType(CoinTosserEvent.Toss::class.java)

private val tossSideEffect = Single
.timer(1, TimeUnit.SECONDS)
.map {
when
{
Math.random() < .5 -> CoinTosserEvent.Heads
else -> CoinTosserEvent.Tails
}
}

override fun
subscribe() = publish(
tossEventsObservable.flatMapSingle { tossSideEffect }
)
}

Notice CoinTosserModel extends from StateEventModel, which takes the initial State of the View and a Reducer as its constructor’s parameters.

And now I will explain what is probably the most complex part of this article: binding Events to Side-Effects and Side-Effects to Events.

  • The first thing we do is obtaining an Observable of a specific Event type (Toss in this case).
  • Then, we define the Side-Effect to be triggered by that Event type and make sure its possible results are mapped to Events (Heads or Tails in this case).
  • Finally, we bind the Event to the Side-Effect and publish the resulting Event back into the Event stream.

The definition of our Model is complete. We can now use it in our ViewModel:

class CoinTosserViewModel : ViewModel() {
private val model = CoinTosserModel()
private val disposable = model.subscribe()

val liveState: LiveData<CoinTosserState> =
LiveDataReactiveStreams.fromPublisher(
model.stateObservable.toFlowable(
BackpressureStrategy.LATEST
)
)

override fun onCleared() {
super.onCleared()
disposable.dispose()
}

fun onToss() {
model.publish(CoinTosserEvent.Toss)
}
}

We expose the State to the View as LiveData so we do not have to deal with the intricacies of the Activity (or Fragment) life-cycle. We subscribe to the Model and dispose of the subscription when the ViewModel is cleared to avoid leaks that could be produced by ongoing asynchronous work related to the Side-Effects we trigger.

The onToss() method will be called by the View as you can see below:

class CoinTosserActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_coin_tosser)

val viewModel = ViewModelProviders.of(this)
.get(CoinTosserViewModel::class.java)
viewModel.liveState.observe(this, Observer {
it
?.apply {
statusTextView.text = when{
isTossing -> getString(R.string.tossing)
isHeads -> getString(R.string.heads)
else -> getString(R.string.tails)
}
tossButton.isEnabled = !isTossing
}
}
)

tossButton.setOnClickListener { viewModel.onToss() }
}
}

The View observes the State and updates accordingly. When the Toss button is clicked, it lets the ViewModel know.

That’s it! The cycle is complete. I hope this approach opens up more possibilities to what you can achieve in your apps. Happy coding!

Android Developer since 2010