Better deep links handling in modular iOS App

Nikita Belopotapov
5 min readMay 31, 2021

--

Deep links is powerful and underestimated mechanism for routing from outside of your app. But what if we could use it for in app routing? Routing is one of the most complicated problems iOS engineers (and Android) face in every day live.

Today i will try to suggest one more way, we could solve this problem.

Deep links

Firstly let’s find out what deep link is. Deep link is a link that points to some feature in your app. When user taps some link, operating system can resolve that app can handle it without opening browser. There’re tons of tutorials which help you to set up your app and use this mechanism, so we’ll miss this topic now. But if you are not familiar with this topic, do not worry, full source code is available on Github

Let’s start from problem formulation

I think that most of iOS developers use deep links in their apps. Usually it turns into a huge if else statement with string parsing from url. Such a code is hard to maintain, scale and much more important — test. Let’s imagine how common AppDelegate file with few deep links looks like.

Huge parsing method

In the best scenario, all this transition stuff moves to another entity like overloaded router. Further, the problem can be aggravated by the existancy of the modular architecture in the application. Then deep links
generate big code dependency, in some case programmers decide to move deep links implementation on the highest level of app, in target app. In this article we will try to implement mechanism which will let handle deep links much more flexible and independent.

What do we want to achive
— Incapsulate logic of one transition into one independent entity.
— Try to avoid huge if else statements
— Enable deep links testing
— Make deep links suitable for modular architecture

Modules

Let’s start from the most irrelevant topic of this article and implement primitive modular architecture

Module definition

We have just created new protocol called FeatureModule and added there two required methods which we will need later. All application modules will implement this protocol. Next we have created new BookmarksModule and implemented required methods. That’s it for modules, it’s clear that it’s not completed modular architecture, but that’s enough for our example.

Know head back to deep links

Endpoint

For the beginnig, i suggest a new definition, endpoint — object performs one transition.

Endpoint protocol definition

isAvailable — variable for some kind of transition availability check. May be you are using Firebase remote config and want to disable some transition. Or endpoint can be performed after some app events.

startTransition(in navigation: UINavigationController?) — as it clear from function name, transition should be performed within this method.

Simple endpoint definition can look like this

Endpoint definition

RoutingAssembly

After module definition we should give it possibility to provide endpoints it can handle. Let’s create protocol called RoutingAssembly with necessary methods. Module conforms this protocol and after it can be used as deep links assembly

Here we can choose within two different ways. The first method — assembly provides deep link by request, after transition started. The second one — assembly provides all deep link which it can handle. All this methods have pros and cons. First method gives your more flexibility if you are using lazy dynamic library loading. It gives module much more time to prepare internal entities. Second one provides all deep link on start of app and router wouldn’t make extra calls to module, which can be overloaded with business logic. In this article we will cover first method.

RoutingAssembly conformance

Handling endpoint availability

As you probably remember, we have computed variable isAvailable which returns Bool in RoutingAssembly protocol. For this simple example let’s create struct

Mock availability facade

Then we should create variable for this struct in our module.

Availability instance

and finally create method for availability checking

Endpoint availability checking

After this we can edit isAvailable in Endpoint

Endpoint availability

Router

Router is main part of this mechanism, so let’s dive into it.

func append(assembly: RoutingAssembly) — method for passing deep links assemblies to router, which was covered few paragraphs before.

startTransition — method to make router prepare transition and start it

set(navigation: UINavigationController) — sets navigation controller to push UIViewController from selected transition

and finally set(errorHandler: RoutingErrorHandlerProtocol) — methods sets entity that handles all errors during transition

Last three methods are very straightforward, they only sets passed variables into internal stored variables or adds assembly to stored array. startTransition method is a little bit more complicated, so let’s dive into it.

Applying knew knowledge

Know, after all preparations, we can rewrite AppDelegate class and get some this like this.

Adding assemblies to router

When application receives deep link, we should handle it.

Handling openURL for UIApplication

And finally, start transition

Start transition within standard mechanism

Final results

Full source code is available on Github

--

--