How to Handle Deep Links in Android (Kotlin)

Learn how to configure your Android app in Kotlin to handle both custom URL │ schemes and Android App Links, directing users to specific content.

Intermediate

Handling deep links in Android (Kotlin) involves configuring your AndroidManifest.xml and then processing the incoming Intent in your designated Activity. There are two primary types of deep links:

  1. Deep Links (Custom Schemes): These use custom URI schemes (e.g., myapp://product/123).
  2. Android App Links (HTTP/HTTPS URLs): These use standard web URLs (e.g., https://www.example.com/product/123) and offer automatic verification, allowing your app to open directly without a disambiguation dialog if verified.

Here's how to implement them:

1. Configure AndroidManifest.xml

You need to declare an <intent-filter> in the Activity that will handle the deep link.

For Custom Scheme Deep Links:

<activity android:name=".MainActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Custom scheme deep link -->
        <data android:scheme="myapp"
              android:host="example.com"
              android:pathPrefix="/product" />
    </intent-filter>
</activity>
  • android:exported="true": This is crucial for any activity that can be launched by an external app or the system.
  • android.intent.action.VIEW: This action indicates that the activity can display data to the user.
  • android.intent.category.DEFAULT: Allows the activity to be the target of an implicit intent.
  • android.intent.category.BROWSABLE: Allows the deep link to be opened from a web browser.
  • <data>: Defines the URI scheme, host, and optional path. In this example, myapp://example.com/product/123 would be handled.

For Android App Links (HTTP/HTTPS Deep Links):

<activity android:name=".MainActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- App Link (HTTP/HTTPS) -->
        <data android:scheme="http"
              android:host="www.example.com"
              android:pathPrefix="/product" />
        <data android:scheme="https"
              android:host="www.example.com"
              android:pathPrefix="/product" />
    </intent-filter>
</activity>
  • android:autoVerify="true": This attribute tells Android to verify that your app is the default handler for the specified HTTP/HTTPS links. This verification requires setting up Digital Asset Links on your website.

2. Handle the Incoming Intent in Your Activity

In your Activity (e.g., MainActivity), you'll retrieve the Intent data and parse the URI.

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.util.Log

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) // Your layout

        handleIntent(intent)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        // If the activity is already running and a new deep link comes in
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        val appLinkAction = intent?.action
        val appLinkData: Uri? = intent?.data

        if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) {
            Log.d("DeepLink", "Deep link received: $appLinkData")

            // Example: myapp://example.com/product/123 or https://www.example.com/product/123
            val pathSegments = appLinkData.pathSegments
            if (pathSegments.size >= 2 && pathSegments[0] == "product") {
                val productId = pathSegments[1]
                Log.d("DeepLink", "Product ID: $productId")
                // Now you can navigate to the product details screen or load data
                // For example: navigateToProductDetails(productId)
            } else {
                Log.d("DeepLink", "Unhandled deep link path: ${appLinkData.path}")
            }

            // You can also get query parameters
            val param1 = appLinkData.getQueryParameter("param1")
            if (param1 != null) {
                Log.d("DeepLink", "Query parameter 'param1': $param1")
            }

        } else {
            Log.d("DeepLink", "No deep link data found or action is not VIEW")
        }
    }
}
  • onCreate(): The first time the activity is launched via a deep link, the Intent will be available here.
  • onNewIntent(): If the activity is already running (e.g., singleTop launch mode) and a new deep link is triggered, onNewIntent() will be called with the new Intent. It's important to handle the intent in both places.
  • intent?.data: This Uri object contains the full deep link URL.
  • appLinkData.pathSegments: Provides a list of path segments from the URI (e.g., for /product/123, it would be ["product", "123"]).
  • appLinkData.getQueryParameter("paramName"): Allows you to extract specific query parameters from the URL.

3. Digital Asset Links (for Android App Links only)

For android:autoVerify="true" to work, you need to host a assetlinks.json file on your website at https://www.yourdomain.com/.well-known/assetlinks.json. This file proves that your app owns the domain.

A typical assetlinks.json looks like this:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.your.package.name",
    "sha256_cert_fingerprints": ["YOUR_APP_SHA256_FINGERPRINT"]
  }
}]

You can find your app's SHA256 fingerprint using the following command (for your release keystore):

keytool -list -v -keystore your_release_key.keystore

Or for your debug keystore:

keytool -list -v -keystore ~/.android/debug.keystore

Testing Deep Links

You can test deep links using adb:

For Custom Scheme Deep Links:

adb shell am start -W -a android.intent.action.VIEW -d "myapp://example.com/product/456" com.your.package.name

For Android App Links:

adb shell am start -W -a android.intent.action.VIEW -d "https://www.example.com/product/456" com.your.package.name

Replace com.your.package.name with your actual application package name.