How to manage a complex DeepLinks scheme on your Android App

Paolo Brandi
AndroidPub
Published in
5 min readMay 27, 2019

DeepLinks are essential key elements to a company marketing strategy to engage new customers and bring them straight to your App as much as to retain those users, who are already customers, providing a better experience and combining web and native App content together under the same ecosystem.

As a result of web contents growing, URLs are expanding and becoming really complex supporting different domains/subdomains, HTTP/HTTPS, dynamic and localized paths etc…

Defining DeepLinks in the AndroidManifest.xml

If you are familiar with the Android Manifest and Intent Filters you should recognize this:

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>

<data android:scheme="app_scheme"
android:host="app_host"
android:path="/path_example" />
</intent-filter>

this is how a deep-link as app_scheme://app_host/path_example is defined on Android in order to be handled by the App. It might be a native deep-link, like the one in the snippet or a Web URL which might have many variations, as mentioned before, and can make it hard to manage.

What is the problem?

Let’s say we have to handle some web URLs which point to 10 different pages of your web site, which means you now have to support 10 different paths, one of each URL. You end up with an intent-filter made of 10 </data> entries. Not bad so far… but what if for each of them you have to support both http and https then you end up with 10*2 = 20 entries

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>

<data android:scheme="http"
android:host="www.example.com"
android:path="/path_example" />
<data android:scheme="https"
android:host="www.example.com"
android:path="/path_example" />
</intent-filter>

yes, your manifest is getting bigger now! But we can make it worse… what if you have to support 2 domains www.example.comand m.example.com. Now you have 20*2 =40 entries… all of them in your AndroidManifest.xml!

It is pretty clear what I want to point out. This number can be much higher and your manifest can contain lots of lines of code only to define deep-links, which can be really difficult to maintain at some point.
Take into consideration that this is only an example of a simple use case, with simple static paths and two domains.
Paths can easily increase the number of </data> entries depending on the structure of the path itself which can variate from case to case.

but… Android provides wildcards and regex for path/pathPattern/pathPrefix?!

Yes and no! Unfortunately Android doesn’t really support regex as anyone could expect, but a really limited and obsolete set of rules to help developers define some patterns. In some cases it might help, in other cases, it might not. It doesn’t provide the flexibility developers should benefit from, but instead, it forces developers to stick with a static set of rules to define patterns to match any possible URL variation.

You can find more details here

A use case…

just to give you an example (from a real use case I had to deal with)…
let’s say we are deep linking from just 5 main hosts, supporting at least 5 different domains/subdomains, both http and https, with many dynamic path variations based on the type of the link. For example, you URL might contain a country code, language code, etc… anything! In my use case, this list eventually ended up with ~500 </data> entries in the Manifest.
I need to have a solution to manage this!

What is the solution?

Basically, the idea is to dynamically build the intent-filter and inject this in the AndroidManifest.xml.

I was looking for a way to generate these intent filters at build time and I ran into this https://github.com/castorflex/manifestreplace-plugin.

To inject anything in the AndroidManifest.xml we can use the manifestPlaceholder functionality provided by the Gradle android-plugin, which exactly allows us to inject anything we want into the manifest. We will modify our build.gradle file accordingly.

But first, our manifest will look like something like that:

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
${deepLinks_manifestPlaceholder}</intent-filter>

where ${deepLinks_manifestPlaceholder will be the key which will be replaced later with an intent-filter definition.

In your build.grade file, placeholders are set up as an array of key-value pairs. For our case, we only have one pair where the key is the placeholder defined in the manifest and the value is the result of a function generateData() which builds our intent-filter.

We place it in the defaultConfig block because we want this to be applied to any build type or flavor. You can also change this behavior based on your needs.

android {

defaultConfig {
manifestPlaceholders = [deepLinks_manifestPlaceholder: generateData()]
}
}

Now we need to implement our function which will generate our injectable collection of <data> as a string, like you would do it if it was a manifest file, but this time it is done at build time using a Gradle script.

Let’s keep it simple just for the purpose of this article. You can have something like that, which will generate both http and https <data> providing a host and a path for each entry you want to add.

static def generateData() {
return data("www.example.com, "path_example_1") +
data("m.example.com, "path1") +
data("www.example.com, "path_example_2") +
data("m.example.com, "path_example_3")
}static def data(String host, String path) {
return http(host, path) + https(host, path)
}

static def http(String host, String path) {
return "\n" +
" <data\n" +
" android:host=\"$host\"\n" +
" android:pathPattern=\"$path\"\n" +
" android:scheme=\"http\"/>\n"
}

static def https(String host, String path) {
return "\n" +
" <data\n" +
" android:host=\"$host\"\n" +
" android:pathPattern=\"$path\"\n" +
" android:scheme=\"https\"/>\n"
}

It would be much better to extract this to a separate Gradle file to keep everything well organized.

We have not finished yet…. when will this bit be generated and injected in the manifest? Good question!

We need to add one more step to the processManifest gradle task to do the job for us. Let’s have a look.

and here we are as a result of your Gradle build you will see your auto-generated AndroidManifest.xml under:

<your_project_folder>/build/intermidiates/manifests/full/<your_flavor>/<your_build_type/AndroidManifest.xml

Enjoy!

References: Antoine Merle on GitHub

--

--