Navigation in Modular Applications with Deep Linking

Damian Burke
6 min readFeb 25, 2019

Motivation

As time goes by applications tend to grow. Growing from a minimum viable product (MVP) to a feature-rich product is an exciting phase in each company/startup — both from a business as well as from a development point of view.

If you’ve started as a sole developer or in a team of two it is very likely that you have a good understanding of the code base. You know the architecture, you are familiar with design patterns and the choices that were made. You are able to split tasks between your teammates based on which parts of the code base they touch to minimize conflicts in your work.

As the number of developers grows, it makes sense to split the responsibilities and/or parts of the app and divide them between the groups. If your application has a certain set of features or if you’re going for a growth vs. retention split doesn’t matter. Looking at the code base it is easy to identify a certain set of layers, which (if we look at vertical layers) could look like this:

Example of a vertically layered software architecture.

For smaller teams it is perfectly fine to work on a code base like this. As the team grows, more and more conflicts will happen during development and the need for additional separation will come up. This is usually where the switch from layer-based packaging to a feature-based packaging or modularization comes in.

Introducing horizontal layers

Implementing horizontal layering on top of a certain set of core modules. This means that a feature module contains all code related to the feature, from the data layer to the presentation and view layers. This allows developers to work on features independently, most of the time without the need to work on code outside of their scope.

Navigation in Android Applications

Navigation in Android apps is usually pretty straight forward. If you have multiple Activities, you simply create an Intent for the targeted Activity, add some extras and start it through the available Context object. If your application is Fragment-based with a single Activity its even simpler — just replace the Fragment.

These approaches have a certain drawback: They require tight coupling of the components. If you have a main Activity which is supposed to start a detail Activity, it has to know about the location of the detail Activity’s class. If you abstract this into a Navigator class the implementation of this class needs to know about all possible screens.

Splitting the app horizontally means we want to remove dependencies between the modules. Standard navigation in Android requires tight coupling, meaning each module is required to know about other modules that are going to be available through navigation.

Solution Requirements

Trying to find a solution that is more suitable for an increased team size, we can come up with a couple of requirements.

Flexible

We want to be flexible and support multiple types of navigation:

  • Intent-based navigation for multiple Activities
  • FragmentTransaction-based navigation for single Activity
  • Other types of navigation (for example for View-based applications)

Parsing and processing the links should be independent from the actual navigation. Our processing should happen in a separated layer and delegate the actual navigation to a different party.

Decentralized Processing

Since the goal is to allow teams to work independently we want to avoid having a centralized link parser. We want each module to be able to supply the application with module-relevant link parsing and navigation events.

Dynamic

Thinking about A/B testing and dynamic feature loading it would be beneficial to be able to register (or unregister) link processors in runtime. Google has announced dynamic feature modules which would play together with this.

Dynamic feature modules allow you to separate certain features and resources from the base module of your app and include them in your app bundle. Through Dynamic Delivery, users can later download and install those components on demand after they’ve already installed the base APK of your app.

Once a deeplink for a dynamic feature is processed, the app can either trigger the download of the feature module or if it is already present, simply execute the navigation action.

The Deeplink Approach

Looking for an alternative approach to navigation, deep linking comes to mind. Most applications require deep linking at some point to redirect the user to certain points in his journey or to point the user towards certain actions so utilizing deep linking for general navigation within the app makes sense.

Why use Deep Linking?

Deep Linking is a platform-independent approach. Android, iOS and web apps can use them. Links are a specified format that offers a lot of customization and an easy way to pass information through schemas, hosts, path segments and queries. This again has multiple benefits, it allows for scoping by feature-path and semantic linking (human readable URIs).

Example Implementation

The basis for our deep linking will be a DeeplinkHandler interface, which does nothing except

An implementation of the DeeplinkHandler interface that is able to handle multiple DeeplinkProcessors looks like this:

The DefaultDeeplinkHandler loops over a Set of DeeplinkProcessors, searches for a processor that matches the input and executes the processor if found. If no matching processor is found, it returns false to indicate that the link could not be processed.

The DeeplinkProcessor interface:

For convenience and standardization it exposes an EXTRA_KEY constant, which can be used to store data (for example a Parcelable) in the navigation action.

A simple example implementation that does nothing except for matching for a link and starting a certain Activity:

An instance of this processor will be passed to the default DeeplinkHandler instance to enable matching and processing the links. This processor matches any link that contains the keyword deep/content and starts the DeepContentActivity.

Intents allow us to also pass data. If we construct a link with variables, for example a name, we can extract it from the link for example through String operations or by parsing the link into URI objects:

This processor matches links that contain deep/custom/. If a matching link is found, the prefix consisting of the schema, host and first path segment deep://deep/custom/ will be removed from the String. The first remaining path segment will be consumed as a name variable.

The name will be passed to a CustomDeeplinkModel (Parcelable) which will be added to the created Intent — and will be consumed by the started Activity.

This very simple setup allows us to dynamically add DeeplinkProcessor instances. Each instance is able to handle certain links and to execute certain navigation actions. The processor implementations in this example hold a reference to the application’s Context. If for example navigation actions should be done through fragment transactions, the processors could also hold a reference to a FragmentManager or simply emit navigation events to a navigation delegate that keeps track of the FragmentManager.

To be able to process all incoming deeplinks, an entry in the AndroidManifest.xml is required.

The referenced Activity extends our DeeplinkActivity:

And provides the DefaultDeeplinkHandler in the implemented getDeeplinkHandler function.

Connect the Dots with Dagger

A simple way to utilize Dagger is to Bind each DeeplinkProcessor into a Set.

This can be done from different modules with as many processors as required. To utilize the Set simply create the DeeplinkHandler through Dagger as well.

Each feature module can now expose its DeeplinkProcessor implementations through a Dagger module, and they will automatically be included in the Set consumed by the DeeplinkHandler.

This of course requires each feature module to have Dagger as dependency and to expose a Module to the Component.

Alternatives

There are a couple of libraries published on GitHub that allow you to utilize deep linking-based navigation. For example DeepLinkDispatcher by airbnb.

The Navigation Android Architecture Component also supports deep linking b̶u̶t̶ ̶a̶t̶ ̶t̶h̶e̶ ̶m̶o̶m̶e̶n̶t̶ ̶i̶t̶ ̶i̶s̶ ̶s̶t̶i̶l̶l̶ ̶i̶n̶ ̶a̶n̶ ̶A̶l̶p̶h̶a̶ ̶v̶e̶r̶s̶i̶o̶n̶ which will most likely see a stable release soon.

If you liked this post, want to discuss it or simply stay in the loop — follow me on twitter:

📝 Read this story later in Journal.

🗞 Wake up every Sunday morning to the week’s most noteworthy Tech stories, opinions, and news waiting in your inbox: Get the noteworthy newsletter >

--

--