Flow: an evolution of Coordinator for iOS

Defining Flows to allow composition of smaller ones to create a more complex Flow

Davide Lorenzi
Aug 28, 2019 · 5 min read
Image for post
Image for post

Introduction

We have already seen many articles about Coordinators, each one bringing a different flavour. The core idea, though, remains the same.

A Coordinator is a component which handles the presentation of different view controllers.

This way we can decouple view controllers from knowing about each other (increasing reusability also).
At the same time, a Coordinator encapsulates logically a set of view controllers, creating a new module, which is easier to reason about.

At TransferWise, we already use the concept of flow throughout the company, which is a sequence of steps (screens and user interactions) to achieve a final result.
For example, the Transfer Flow is in charge of gathering all the data in order to send a payment from A to B: this encapsulates a set of different operations.

A flow can be started from various places within the app: for example, the Transfer Flow can be started from the ‘New transfer’ button on the main screen, from a previous transfer by repeating it or from the Balance Account.

Sometimes you’ll need to nest another flow inside the parent flow to achieve your goal.
For example, the Transfer Flow has to eventually start the Profile Flow, to get more information about the user, before proceeding with its original intent.

Requirements for a Flow

With all of these criteria listed above, the rough requirements for a coordinator — what I call a flow — are these:

For these flows, we also want to have:

An important note to make is that I wanted all this to NOT be tied to any particular design/architecture pattern.
In some examples, I may sometimes refer to some VIPER components (which is what we mainly use at TransferWise), but the Flow as a concept does not depend on any particular design pattern.

Related parts

This series is split in 3 parts for easier understanding, broken down to:

All the code shown here is already being used in production code. This works great for us, but there are of course possible improvements or things that may work better in a slightly different way for you.

Building blocks

Flow

A Flow can be seen as a module which, once started, is expected to terminate producing a result. The caller should make no assumptions on how the Flow is working: this way, we can really make it reusable in different scenarios.

FlowHandler

Through the FlowHandler, a Flow communicates back to the caller when it starts or finishes, providing also the result and a way to dismiss what the Flow presented.

This is just a wrapper, initialised passing two callbacks:

ViewControllerPresenter & Dismisser

These components are used to abstract how the view controllers are presented/dismissed.

With these interfaces we get a couple of benefits:

We also have a factory which provides us these components:

To be able to write synchronously unit test, this factory can be injected in the Flow.
This way, we can provide a mocked version that won’t actually present any view controller on screen, but simply put them in an array. Our unit tests could then simply check that array and assert any required condition.

FlowInspectable

This only exposes the type of the sub-Flow active at any given moment.

Note: we could eventually make this protocol expose the instance itself, rather than the Flow result type, if we want to be able to have more precise unit tests.

FlowController (for sub-flows)

Finally, for when we need to handle sub-Flows, we have an additional component which can help us write less and more robust code:

It implements the FlowInspectable protocol, and adds a function. Its signature contains all the parameters required by the FlowHandler, which this FlowController is creating and assigning to the Flow passed as parameter.
Its responsibility is to keep a strong reference to the Flow being presented, and release it when it completes.

This could have been done manually by the object launching a new flow, but I found it a bit harder to master/remember for new people approaching the Flow idea, and more error prone too.

Conclusion

What we did cover in this first article:
- the definition of our problem
- the actual features we want to implement
- the basic building blocks and their purpose to create flows

You can find the complete project containing all these codes here.

In the second part we’ll see a concrete example of how to create a flow from scratch.

TransferWise Engineering

Posts from @TransferWise’s Engineering Team

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store