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

-based navigation for multiple FragmentTransaction -based navigation for single Activity

-based navigation for single 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 URI s).

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 DeeplinkProcessor s looks like this:

The DefaultDeeplinkHandler loops over a Set of DeeplinkProcessor s, 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 .

Intent s 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.