Using FlowKit in a N26 Product

Giulio Lombardo
InsideN26
Published in
8 min readJan 17, 2023

--

It’s a situation you’ve probably been through.

You build a tailor-made product taking into account all the needs, legal requirements and functionality required by targeting a specific group of customers or a particular country. The product is released and you’re already looking to its future: expand the user base and make it accessible to new markets.

You get back to work looking for the differences between what you just published, new requirements and the possible solutions to implement everything.

At first it all seems feasible, but shortly after having listed all the possible combinations of the product, due to the different requirements for the new users, you realise that managing everything, without risking breaking the already published product, will be difficult.

It’s a hard truth: Scaling up products isn’t easy. And this is even more true and obvious when you have multiple platforms to manage, mostly because the chances of something getting out of hand are directly proportional.

N26 Instalments

We also found ourselves in the same situation with N26 Instalments, but this time we had an ace up our sleeve to solve the scalability problem: FlowKit.

To understand how we used FlowKit, let’s first start by taking a look at the original product that did not use it.

The original product

At that time, the product consisted of a few screens connected to each other without particular conditions. In fact, the main focus was the German market and we did not know if it would be available in other markets in the future.

In the diagram below you can see how it looked like:

The new requirements

The product worked well and we wanted to publish it in new European markets, specifically Italy and France. This was great news! Although, at the same time, we realised that these two markets had specific laws that required additional screens or different content in the existing ones. In short, to reach these new markets we had to make the product scalable.

The Italian Flow

eSignature

The Bank of Italy’s requirement was clear: customers applying for a loan must approve it with a digital signature, so we had to add a new step dedicated to the eSignature, visible only to Italian customers, to keep the flow as it was for the previous customers.

The French Flow

A single offer screen

As for French clients, according to other legal requirements, we could not show more than one offer to the client when it came to small loans, as in our case. Furthermore, no message was required prior to the confirmation of the offer, unlike the previous version of Flow. For this reason the content of the Offer Selection Screen had to be made dynamic to show this new “single offer” mode.

Getting it done

Presentation Gateway

The first problem to be solved was customising the content based on the user’s region.

To solve it we could insert controls in the two FE platforms (Android & iOS) in order to control the region and modify the contents shown. However, this solution already presented problems. In fact, by duplicating the control in the platforms, we could create two separate points of failure, and also in order to format the data according to the customer’s region we needed new data compared to those already provided.

So we looked elsewhere, and came up with a new and better solution: the Presentation Gateway.

The purpose of this new tool was to centralise the creation of content according to various conditions (currency, region, interest rates) for the FE platforms, so as to remove all logic and have a single source of truth. Thanks to this, we were able to open the doors to a new concept: to generate navigation to BE, for all FE platforms, and here FlowKit was able to express its full potential.

FlowKit

Having now data already formatted and ready to use coming from the BE through the Presentation Gateway, the FE platforms only had to make the latest changes to receive the new dynamic navigation Flow and execute it.

In order to do this, we’ve converted the existing screens, and the screens for the new regions in FlowKit Steps. This enabled us to present them to the user in the order established by the Back-End and only when it indicated it was necessary by it.

All this, without adding any conditions in the FE platforms. This translates into: central source of truth, less code, fewer parts to test and fewer places where logic can create problems.

FlowKit allowed us to reach a win-win situation.

The Baseline

Now that we have seen together the scalability solution that uses FlowKit, the same solution that we have chosen to implement the new requirements in the real world, we can compare the traditional solution afterwards to understand what the differences and strengths are by going into detail. To compare the two solutions in the most effective way, we can take a problem and understand how they solve it respectively.

Let’s take for example the navigation management in case a user has a bank account in Italy.

“Standard” way

To manage navigation for customers with German, French and Italian current accounts, we must provide data to the FE platforms which makes it possible to discern the nationality of the current account.

This information will be used in navigation operations, possibly in every point, as the screens will have to “get to know each other” in order to present.

Let’s look at some sample code to better understand how this translates into:

enum Country {
case germany
case italy
case france
}

class InstallmentsOfferConfirmationPresentation {
func navigateToNextScreen(country: Country) {
switch country {
case .germany:
showPINInput()

case .italy:
showESignature()

case .france:
showPINInput()
}
}

func showESignature() {
/* Presentation Code */
}

func showPINInput() {
/* Presentation Code */
}
}

class InstallmentsOfferSelectionPresentation {
func navigateToNextScreen(country: Country) {
switch country {
case .germany:
showADM()

case .italy:
showADM()

case .france:
showOfferConfirmation()
}
}

private func showADM() {
/* Presentation Code */
}

private func showOfferConfirmation() {
/* Presentation Code */
}
}

If the code already looks complicated and tangled now, imagine how it will become more complex as future nations are added to the product. The more checks, the more difficult it gets to read the code and write or run tests.

FlowKit

Using FlowKit the only part of the code that will increase will possibly be only the StepFactory, as the order of the Steps in the Flow does not affect the lines of code since the link is dynamic and not based on the position.

As we can see below:

enum StepType: String {
case marketing
case offerSelection
case adm
case offerConfirmation
case pinInput
case eSignature
}

protocol InstallmentsStepHandlerFactoryType {
func marketingStepHandler() -> StepHandler<MarketingStepDefinition>
func offerSelectionStepHandler() -> StepHandler<OfferSelectionStepDefinition>
func admStepHandler() -> StepHandler<ADMStepDefinition>
func offerConfirmationStepHandler() -> StepHandler<OfferConfirmationStepDefinition>
func pinInputStepHandler() -> StepHandler<PINInputStepDefinition>
func eSignatureStepHandler() -> StepHandler<ESignatureStepDefinition>
}

struct InstallmentsStepFactory: StepFactory {
typealias OUTPUT = Empty
typealias STEP = FlowStep

private let stepHandlerFactory: InstallmentsStepHandlerFactory

init(
stepHandlerFactory: InstallmentsStepHandlerFactory
) {
self.stepHandlerFactory = stepHandlerFactory
}

func makeHandler(
for stepRawType: String
) -> AnyStepHandler<STEP, OUTPUT>? {
guard let stepType = StepType(rawValue: stepRawType) else {
return nil
}

switch stepType {
case .marketing:
return AnyStepHandler(
stepHandlerFactory.marketingStepHandler()
)

case .offerSelection:
return AnyStepHandler(
stepHandlerFactory.offerSelectionStepHandler()
)

case .adm:
return AnyStepHandler(
stepHandlerFactory.admStepHandler()
)

case .offerConfirmation:
return AnyStepHandler(
stepHandlerFactory.offerConfirmationStepHandler()
)

case .pinInput:
return AnyStepHandler(
stepHandlerFactory.pinInputStepHandler()
)

case .eSignature:
return AnyStepHandler(
stepHandlerFactory.eSignatureStepHandler()
)
}
}
}

struct InstallmentsStepHandlerFactory: InstallmentsStepHandlerFactoryType {
func marketingStepHandler() -> StepHandler<MarketingStepDefinition> {
/* Presentation Code */
}

func offerSelectionStepHandler() -> StepHandler<OfferSelectionStepDefinition> {
/* Presentation Code */
}

func admStepHandler() -> StepHandler<ADMStepDefinition> {
/* Presentation Code */
}

func offerConfirmationStepHandler() -> StepHandler<OfferConfirmationStepDefinition> {
/* Presentation Code */
}

func pinInputStepHandler() -> StepHandler<PINInputStepDefinition> {
/* Presentation Code */
}

func eSignatureStepHandler() -> StepHandler<ESignatureStepDefinition> {
/* Presentation Code */
}
}

///

protocol StepFactory {}
struct Empty {}
struct FlowStep {}

struct AnyStepHandler<A, B> {
init(_ stepHandler: StepHandler<A>) {}
}

struct StepHandler<T> {}
struct MarketingStepDefinition {}
struct OfferSelectionStepDefinition {}
struct ADMStepDefinition {}
struct OfferConfirmationStepDefinition {}
struct PINInputStepDefinition {}
struct ESignatureStepDefinition {}

Scaling even more

Making your product scalable using FlowKit not only makes navigation management easier by removing conditions in your code, it also opens the door to new scalability solutions and opportunities.

A/B Test

Having a central source of truth in the Back-End for both platforms also means being able to enable A/B Testing easily and simultaneously, with one simple switch.

In fact, by using the solution explained above, it will be easy in the future to add new Flows to be used according to an A/B Test logic, for example: test a new version of the product with only 50% of users, while keeping the experience the same for the other half.

This is also possible to achieve without FlowKit, of course, but the code will necessarily have to rely on (repeated) checks in the navigation, because it will be linked to individual screens. In FlowKit on the other hand, screens are Steps part of a dynamic Flow and without direct links, making it easier to react to changes.

Future “proof-ness”

Another potential benefit that you get for free from this implementation is the ability to modify the Flow without releasing a new version of the FE applications.

If the FE applications are already able to manage a stepType, one can decide to add more Steps with it in a given Flow, or even add future stepTypes to be used at a specific moment in time by just updating the Flow remotely using the Back-end. By doing this you would make features unlinked from application releases.

In other words, you can make FE applications more “future-proof” and app version agnostic by supporting new stepTypes to use later, or reusing old ones.

Conclusions

Scaling products is certainly not an easy task and there is no silver bullet to use in all situations. You have to take into account many behaviours that may be non-linear. If the problem is tackled in an innovative way, such as the decomposition of Flow into Steps with the use of FlowKit, many difficulties can be set aside.

--

--