iOS Screen Navigation Engine at Revolut

Vincent Berihuete
Revolut Tech
Published in
3 min readAug 17, 2022

Ever heard about the Coordinator pattern? Good for you, this article isn’t about it. I’m here to open your eyes to Custom Navigation Engines just like the one we created on our Revolut Flow Engine.

First let’s talk about some traditional approaches for navigation before we get into our Flow engine:

Storyboard actions navigation

Three screens, the first one being a navigation view, the second one being a main sample screen with a button to navigate to the third screen.

The most traditional way of doing navigation with storyboards is the one that’s triggered by an action. As you know, you can ctrl + drag from your button to reach the desired screen on your storyboard.

Segue driven navigation

Four screens shown, first one being a navigation controller the second one the main sample screen the third one shows a button navigation and the fourth one shows a segue assignation by a line drag

Another traditional type of storyboard navigation are segues, by doing ctrl+drag from one view controller to another we create a connection of a certain type (push, modally, etc) and assign it an ID.

Performing a segue with the identifier assigned on storyboard

ViewController code driven navigation

The most traditional way outside storyboards, is to instantiate and push or present your view controllers programmatically.

Instantiating a view controller programmatically and accessing the available navigation controller to show pushed.

Flow Engine

Now let’s bring out the big guns: the abstraction of navigation outside view controllers.

Why do we need to abstract navigation? Well, imagine you have a FoodDeliveryApp and in there you have a screen called ProductDetails that shows the information for a given product (Hamburger 🍔, fish and chips 🍟, etc) — but you want to call this screen and its dependencies from anywhere. Will it be enough to just instantiate the screen everywhere, with its dependencies and its actions? 🤯 of course not! 😅

Wait, what? Don’t worry, here’s some code:

We create a sample ProductDetails View Controller with all values and actions managed through its View Model. We can see here that business actions of the view model are delegated through dependency injection.

When we want to call our ProductDetail screen and pass its business actions through dependency injection, we end up doing this:

Code looks clean to you? Well, kind of 😅. What happens when you need to show that product page from other view controllers and handle its actions? .showRelatedProduct and .addToOrder? Copy and paste this implementation? 😢 Of course not!

What if you could abstract this so you only call some magic method? Let’s say runProductDetailsFlow(productId:_) with actions and dependencies of this screen abstracted in that layer? Well, without further ado:

Basically as the name says, it’s an engine (state machine) to handle a flow (set of steps). A carefully crafted state machine created by our own Revolut Legend Ilya Velilyaev. Let’s start with the backbone of it:

This Flow Engine represents your state machine, with the use of generics it works as the backbone structure to allow your navigation to be step driven.

After having your FlowEngine (feel free to copy and paste that file, and use it to create great flows in your apps 🚀), you can create Flows for your different business cases. Let’s go back to our FoodDelivery app example and create a flow to show ProductDetail with its dependencies and business actions abstracted! 🎉

Flows allow you to define steps and state, the function nextStep easily shows that based on a certain state you will get your next step

Once you create and define yourFlow with its steps and initial State, you need to create aFlowPerformer — the one who will actually execute these steps and their logic.

This Flow Performer takes every step defined on your Flow and allows you to provide code for it. Notice how we use the yet to be defined FlowRunner

When you have your FlowPerformer you can proceed to create your magic maker FlowRunner — this is the one that allows you to call as one liner: flowRunner.runProductDetailFlow(...) :

FlowRunner allows you to Run any business Flow with all its dependencies and business actions abstracted.

Finally from anywhere in your code (most preferably from other flows), you can call as many flows as you like while avoiding code duplication and have an easier TDD approach to your code.

That’s it, hope you enjoyed and that you start controlling, testing and abstracting your navigation in your apps 🚀

--

--

Vincent Berihuete
Revolut Tech

I do iOS. I drink, I eat, a regular human being basically