Introducing PMVP, a Reactive Mobile Application Design Pattern

Aubrey Goodman
Motiv Engineering
Published in
9 min readAug 26, 2018

--

I know what you’re thinking. Another design pattern?! Your company has been using Pattern XYZ since they started building mobile apps. It has worked just fine so far, except all those bugs along the way. Why take the time to re-train all your engineers to use a new tool? Well, sit back and I’ll tell you.

It starts with acronyms.

When I started iOS development in 2008, the best practice was something called Model-View-Controller (MVC). This was one of the first patterns to make a tsunami-sized wave in the global community. Before its adoption in iOS, MVC was the standard for desktop application design. It enabled the separation of critical aspects of the application lifecycle into discrete components whose responsibilities were clear. And there was much rejoicing.

By this time, Javascript was beginning to experience a huge resurgence in the web development community as a server-side language. The last ten years have been an extended golden age for Javascript, with new open source libraries popping up all over the place. During this same period, web browser users grew increasingly demanding of responsive features and reactive components. This ushered in the new hotness, called Model-View-ViewModel (MVVM).

With MVVM, components started reacting in a more fluid way to upstream data changes. With an in-memory store of a data model managing the state of your view hierarchy, browsers could offer unprecedented control to developers. There was suddenly an object in browser memory with all the properties of the view state based on the data model state. This allowed UI developers to build reactive components, where pending changes were assumed to succeed until the response data returned from the remote server provided new information. In this way, reactive design was born.

Drunk on its own euphoric rise to prominence, the MVVM community forked, and a new faction emerged to champion their new pattern, called Model-View-Presenter (MVP). As an engineer building systems with these patterns, I’ve had a hard time keeping them separate. Candidly, when MVP came along, I found myself wondering why it is any different from MVVM or MVC. At their core, the most innovative aspect is the isolation of key behaviors into discrete components with clear responsibilities. The specific nature of the delegation of responsibility seemed situational.

Then, a critical milestone was reached — Reactive Extensions (Rx) were introduced to the developer community. Before Rx was published, multi-threaded systems required ubiquitous consideration for execution context and tools to enable dispatched code to be executed on a specific queue, using some thread-safe mechanism. It was extremely difficult to find engineers with advanced skill in this area. Many excellent best practices emerged for managing task execution in a multi-threaded environment. With the advent of Rx, all that was turned on its head. Rx enables the development of synchronous multi-threaded systems whose components react to external influence. Moreover, Rx enforces a strict declarative approach to system behavior definition. In other words, reactive systems follow pre-defined pathways, triggered by specific notifications.

“When X happens, perform task Y.”

This is highly counterintuitive to industry veterans. Some of us have spent the last 20 years building things that work in a very different way, like this:

“Every N seconds, check the value of X asynchronously and conditionally perform task Y when X satisfies some criteria.”

Hilariously, even the English sentence required to describe the behavior is longer, just as the non-Rx code is typically more verbose than its Rx equivalent. Let that sink in…

Here at Motiv, we have encountered some very complex problems related to real-time data processing and we believe there is a better way than all of the patterns described above. We have devised a pattern — taking advantage of the Rx philosophy — to enable an unprecedented level of clarity in reactive design. We call this Provider-Model-View-Presenter (PMVP).

Overview

Provider

The Motiv ecosystem introduced a new component into the pattern. The Provider component is responsible for owning all lifecycle context for a group of data models. In its simplest form, you might think of it as a template, like Provider<T>, where T is the model type. This is highly flexible. You might have Provider<Array<T>> or Provider<Map<K,T>> or any arbitrarily complex data type. The key is the Provider object owns the lifecycle of all the objects it manages. We found the name “provider” the most universally clear and concise term. There have been flame wars across the internet about words like “manager” and “controller.” Adding fuel to the fire, the word “service” has special meaning on Android.

Model

In this context, Model refers to the ViewModel. The Provider owns the data model, so we can decouple our Model as a dedicated component responsible for view state. I want to be clear about one thing here. “View state” means the state of view components, but not anything like “label6 is visible.” If you envision the view components as being participants in a story, the Model is the storyteller.

View

In PMVP, the View components represent the standard elements used to compose a user interface, such as labels, images, etc. These components have no interactive behaviors associated with them. They are purely driven by presenters.

Presenter

In essence, the Presenter is responsible for updating objects in the view hierarchy. For simple views, this may require only one presenter. For more complex views, it may make more sense to separate groups of view objects into multiple presenters. Also, every Presenter must only be made aware of the ViewModel. In this way, Presenter is always a slave to ViewModel state. Presenters can only subscribe to observables exposed by the ViewModel. By definition, they shall never subscribe to external components; these subscriptions shall be defined in the ViewModel and exposed as derivative observables, if necessary. This guarantees the ViewModel is the ultimate source of truth for all behaviors defined in the UI layer.

PMVP in Action, a Tale of Two Implementations

Now that you’ve had an overview, let’s look at a real world example. We’re going to build a simple interface two different ways and compare them. This helps to highlight the benefits of using PMVP over traditional techniques. For our example, we will build a tamagotchi simulator. Let’s dive right in!

Requirements

Tamagotchi simulate the needs of a living creature as follows:

  • Model shall track food as an integer property.
  • Food shall decrease by one unit per tick.

Non-interactive components:

  • Creature shall emote “HUNGRY” each tick when food is low (<10).
  • Creature shall emote “DEAD” when food supply equals zero.

Interactive components:

  • User can tap FEED button to add ten units of food, but not if creature is dead.

Initial conditions:

  • Creature starts with 5 food.

View hierarchy:

view
- creatureImageView
- creatureStateLabel
- feedButton

Part 1: Traditional Approach

Using traditional approach, we would create a view controller to represent our tamagotchi state. We track the creature food state as a simple integer property on the view controller itself. Then we create a tick method to update the data model, and we invoke the tick method using a timer. This forces us to define logic in the tick method to handle the state of UI elements. It also forces our tick method to be invoked on main queue (because NSTimer needs a run loop to function properly).

class TamagotchiViewController: UIViewController {  @IBOutlet private weak var creatureImageView: UIImageView!
@IBOutlet private weak var creatureStateLabel: UILabel!
@IBOutlet private weak var feedButton: UIButton!
private var food: Int = 20 override func viewDidLoad() {
super.viewDidLoad()
creatureStateLabel.isHidden = true
}
private func tick() {
if food == 1 {
food = 0
emoteDead()
}
else {
food -= 1
if food < 10 {
emoteHungry()
}
}
}
@IBAction func feed() {
food += 10
}

private func emoteHungry() {
creatureStateLabel.text = “HUNGRY”
creatureStateLabel.isHidden = false
}
private func emoteDead() {
creatureStateLabel.text = “DEAD”
creatureStateLabel.isHidden = false
}
}

Part 2: PMVP Approach

With PMVP, we introduce a view model and presenter to separate the responsibilities into discrete components. The food tracking properties move into the view model. Also note the view model exposes observables related to the UI components. Instead of publishing a “food” observable and forcing the presenter to define the “hungry” and “dead” states, we define them explicitly in the view model.

Provider

With PMVP, we must have a component responsible for managing the state of the data model. In this case, we define a CreatureProvider, which owns a Creature model and manages its lifecycle. Note: both public methods include a guard against invalid state; they only emit new state if the current state is valid.

struct Creature {
var food: Int
}
class CreatureProvider {

let creatureSubject = BehaviorSubject<Creature>(value: Creature(food: 5))
private let disposable: Disposable init() {
disposable = Observer<Int>.interval(1.0, scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { [weak self] _ in self?.tick() })
}
func feed() {
guard var creature = try? creatureSubject.value(), creature.food != 0 else { return }
creature.food += 10
creatureSubject.onNext(creature)
}
private func tick() {
guard var creature = try? creatureSubject.value(), creature.food > 0 else { return }
creature.food -= 1
creatureSubject.onNext(creature)
}
}

View Controller

The PMVP view controller has very similar structure to its traditional counterpart. It is responsible for owning references to all the view elements, as well as the model and presenter(s). Also, any interaction logic lives here. Note the inclusion of the tap observer, which delegates to the model.

class TamagotchiViewController: UIViewController {  @IBOutlet private weak var creatureImageView: UIImageView!
@IBOutlet private weak var creatureStateLabel: UILabel!
@IBOutlet private weak var feedButton: UIButton!
private let viewModel = TamagotchiViewModel()
private var presenter: TamagotchiPresenter!
private var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
presenter = TamagotchiPresenter(viewModel: viewModel,
imageView: creatureImageView,
label: creatureStateLabel,
button: feedButton)
}
override func viewWillAppear() {
super.viewWillAppear()
registerObservers()
presenter.registerObservers()
}
override func viewWillDisappear() {
super.viewWillDisappear()
disposeObservers()
presenter.disposeObservers()
}
private func registerObservers() {
feedButton.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.viewModel.feed()
})
.disposed(by: disposeBag)
}
private func disposeObservers() {
disposeBag = DisposeBag()
}
}

Model

Now, the logic for tracking creature state is fully encapsulated into the provider. Changes to the creature data model are published via observable. The view model subscribes to the provider observable and reacts to changes. In this case, the view model tracks the creature’s food directly, binding it to a local BehaviorSubject. Furthermore, the model now also publishes observables specifically intended to drive behavior in the presenter.

class TamagotchiViewModel {
private let foodSubject = BehaviorSubject<Int>(value: 5)
private let disposeBag = DisposeBag()
init() {
CreatureProvider.instance.creature()
.map({ $0.food })
.observeOn(MainScheduler.asyncInstance)
.bind(to: foodSubject)
.disposed(by: disposeBag)
}
func hungry() -> Observable<Bool> {
return foodSubject.map({ $0 < 10 })
}
func dead() -> Observable<Bool> {
return foodSubject.map({ $0 == 0 })
}
func feed() {
CreatureProvider.instance.feed()
}
}

Presenter

This class is responsible for reacting to state changes in the model and updating view elements accordingly. Note: there is no interaction logic being defined here. The observer subscriptions are simple content bindings. All interaction observers are defined in the controller.

class TamagotchiPresenter {
private let viewModel: TamagotchiViewModel
private let imageView: UIImageView
private let label: UILabel
private let button: UIButton
private var disposeBag = DisposeBag()
init(viewModel: TamagotchiViewModel, imageView: UIImageView, label: UILabel, button: UIButton) {
self.viewModel = viewModel
self.imageView = imageView
self.label = label
self.button = button
}
func registerObservers() {
Observable.combineLatest(viewModel.hungry(), viewModel.dead())
.map({ !($0 || $1) })
.bind(to: label.rx.isHidden)
.disposed(by: disposeBag)
viewModel.hungry()
.filter({ $0 })
.map({ “Hungry” })
.bind(to: label.rx.text)
.disposed(by: disposeBag)
viewModel.dead()
.filter({ $0 })
.map({ “Dead” })
.bind(to: label.rx.text)
.disposed(by: disposeBag)
viewModel.dead()
.bind(to: button.rx.enabled)
.disposed(by: disposeBag)
}
func disposeObservers() {
disposeBag = DisposeBag()
}
}

Comparison

Cons

  • Rx Required. Using this pattern will require your engineers to learn Rx.
  • Steep learning curve. While is it incredibly powerful, reactive design is very different from traditional techniques to software design. There are many rough edges, pitfalls, and gotchas, and it can be challenging to identify the source of strange behaviors. Learning Rx can be especially challenging for industry veterans, who must adjust to a “new normal” and think about things backwards and upside down.
  • Verbose. For very simple examples like the one given above, PMVP feels like a little overkill. We must add two new classes, and each class has its own boilerplate requirements for managing disposable lifecycle.

Pros

  • Reactive. The declarative nature of Rx leads to highly readable code, acting more like a recipe than a procedural sequence of commands. Instead of using method invocations to fetch state from external sources, the pattern is inverted; we have observers receiving new values and responding with specific behavior.
  • Team-friendly. The decoupled component architecture enforces a natural integration boundary between components. This makes it easier to delegate development responsibility to team members. One person can build a view model, while another builds a presenter. Moreover, different functional areas within a complex UI can be split up into multiple presenters — one for each area — and built by different team members.
  • Easy to review. Our engineers celebrate the elegance and simplicity introduced into our code review process. PMVP changes are easy to understand in context and decrease the cognitive load on the reviewers.
  • Testable! The introduction of Rx-based view model and presenter make it easy to test the interaction between data and UI layers. Providers can be mocked into test fixtures to exercise the view model observable behavior in response to changes published upstream. This makes it easier to incorporate unit tests into your UI code.

Conclusion

We’re really excited about our new pattern, and we’re using it in all future versions of the Motiv app. It truly crystallizes development of robust components by forcing developers to focus on critical aspects of a design before building anything. It demands a consideration for state machine dynamics and compels engineers to define all the external triggers causing state transition. This helps your team members break down complex interactions into something simple and easy-to-understand.

We couldn’t be happier about PMVP, and we look forward to sharing more articles and screencasts over the next year!

--

--