It’s been almost a year since we started using RxSwift along the Model-View-ViewModel (MVVM) architecture at BlaBlaCar. We’re thrilled with the results. The code we’re writing with this approach is much easier to understand, maintain, test and scale. However, the first weeks were not a piece of cake: we had to iterate on some aspects of our MVVM+RxSwift architecture to get things right. One of them is the way Inputs are provided to ViewModels.
Let’s go through two different approaches to providing inputs (Rx Events) to ViewModels.
But first, let’s quickly talk about ViewModels.
The public contract of a ViewModel is very important. You have to get it right (for more than one reason):
- It should be pluggable on any View, i.e. it is not the way a View is built that is going to define the public contract of a ViewModel. As a reminder, it’s the View that owns the ViewModel. In other words, the View is aware of the ViewModel, not the other way around.
- It should be testable. In the end, one of the biggest benefits of the MVVM architecture is to make the business logic testable.
- MVVM is 💪 with binding mechanisms so let’s leverage that with RxSwift.
A rule of thumb when designing a ViewModel contract is to always try to conceive the ViewModel as a simple black box that accepts inputs in order to produce outputs.
Talking Rx, it means the ViewModel consumes Events (inputs) from some Streams (most of the time provided by the View) to compute output Streams (for the View).
From there, a simple protocol can be written to express that any ViewModel should have Inputs and Outputs.
Next up, 2 different implementations of this protocol, each with their own pros and cons.
Providing inputs to ViewModels
Erik Meijer, creator of Rx, likes to think in a purely functional way. From there, and considering that
Subjects are the mutable parts of Rx, it’s no surprise to find resources quoting E. Meijer saying that using them could be wrong, and that in most cases it’s possible to avoid them.
This is why we started by avoiding using
Subjects when designing ViewModels.
First approach — without Subjects
Like I said, ViewModels are all about transforming Inputs into Outputs. Let’s add that to our
This makes it clear that at some point, the View has to provide all the Inputs (at the same time, we’ll get back to that) to the ViewModel to let it compute the Outputs.
Using this pattern, let’s try to build a simple View that requests the user to type in his name, and hit a button in order to display some greetings:
Now let’s think about the Inputs and Outputs of the ViewModel. This ViewModel, let’s call it
SayHelloViewModel needs to know when the user presses on the “Validate” button, and also what the user entered in the
UITextField. Those 2 things are going to be the Inputs.
Concerning Outputs, we’ll only have one, the one that tells what to display to greet the user.
Here’s the implementation:
The greeting output stream is computed as follows: when “Validate” is hit, we take the last value of the input text field to create the
Hello \(name) string.
Now let’s see how the View (here a
UIViewController to make things simpler) plays with this ViewModel.
We rely on
RxCocoa to get the stream of what’s being entered in the
UITextField name and to know when the button is tapped. With those two streams it’s easy to create a
SayHelloViewModel.Input instance, pass it on to the ViewModel, and get the outputs. From there, the View is working as expected 💪!
Earlier, we mentioned the fact that a ViewModel “should be pluggable on any View”. Let’s see if we can use
SayHelloViewModel with another View. This time we’ll build a view using a
The same view based on a
UITableView looks like this:
Now that the View is based on a
UITableView, we have 3 custom
UITableViewCells: one for the text field, one for the validate button and the last one for the label.
At this point, and after conforming to
UITableViewDataSource, it should just take a few minutes to write
bindViewModel() and be iso with the previous version.
But wait! Now that we’re using a
UITableView, the content of the view is lazily loaded and displayed. There’s no way to synchronously get (at the same time) a reference on the text field and the button. In other words, we can’t create an instance of
func transform(input: Input) -> Output function is simple, clean and there’s no need to use
Subjects but it also has limitations. A ViewModel with such an interface will only work with Views that are able to gather at the same time all the resources needed to create the ViewModel’s Input.
That’s when, in my opinion, it’s totally acceptable to start using
Subjects to design ViewModels that are compatible with any View.
Second approach — with Subjects
Now that we know it’s not always possible to provide all ViewModel’s inputs at a given time, we need to modify the
ViewModelType protocol to make it more flexible. Let’s just expose 2 properties, one for Inputs, the other for Outputs.
This way, the View is completely free to decide when to provide Inputs, and when to subscribe for Outputs.
In the first half of this article, we used the
transform function to create the link between Inputs and Outputs. We still need to create that link, and that’s where
Subjects come into play.
Subject is a kind of bridge that acts both as an
Observer and an
Observable. It is a good solution to move from the imperative programming world into the functional world of Rx.
Knowing that our ViewModel has 2 inputs (the name text field and the validate button), we’ll need 2
Subjects. From there, it is easy to migrate the ViewModel implementation of the previous chapter to conform to the new
A few interesting things to note here:
- The complex part of the ViewModel (computing Outputs), does not change.
Subjectsare private. There’s no way to hack the ViewModel, the only way to interact with the ViewModel is through the public
- This implementation respects all requirements listed at the beginning of the article: it is pluggable on any view, easily unit testable and compatible with Rx bindings.
You can check out the final implementation of the View based on a
UITableView that uses this ViewModel.
The two approaches described in this article are compatible with each other and can both be used in the same project. However, it’s clear that sometimes the first approach does not work with some kinds of views. This limitation is not always clear when getting familiar with MVVM and Reactive programming, and I hope this article is going to help you in your upcoming projects.
You can also check out how Kickstarter design their ViewModels. They create a bridge between Inputs and Outputs using ReactiveSwift’s
Property. It’s very similar to what we did at BlaBlaCar using
Subjects: they keep this
Property private and build all the Outputs in the ViewModel’s initializer.
Thanks for reading! If you liked what you read, feel free to share or leave a comment. You can also follow me on Twitter.