---
title: Implementing Android App Link
url: https://calvin.my/posts/implementing-android-app-link
published: 2025-06-22
updated: 2026-09-14
category: Development
tags:
- Android
- App Link
- Deep Link
summary: The post explains how to implement Android App Links so supported web URLs open directly in an installed app while falling back to the browser otherwise. It outlines configuring an Android activity to handle matching HTTPS paths, reading a URL query parameter in the app, and verifying domain ownership through a publicly hosted association file containing the app package and signing-certificate fingerprint. Testing involves opening the same QR-encoded URL before and after installing a signed app build.
---

# Implementing Android App Link

## Our Goal

1. Users access https://calvin.my/test from their mobile phones.

2. If they have our mobile app installed, the mobile app automatically launches and shows the content.

3. Otherwise, the link is opened in the default browser.

* * *

## Preparing the app

1. Create a new Android app.

2. Edit the AndroidManifest.xml to include an intent filter for the App Link.

```xml
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:theme="@style/Theme.LinkOne">
    <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" />
        <data android:scheme="https" />
        <data android:host="calvin.my" />
        <data android:pathPrefix="/test" />
    </intent-filter>
</activity>
```

3. We will print an ID from the URL for testing purposes.

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        
        val eventId = extractEventId(intent)
        
        setContent {
            LinkOneTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    EventDisplay(
                        eventId = eventId,
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
    
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        val eventId = extractEventId(intent)
        recreate()
    }
    
    private fun extractEventId(intent: Intent): String? {
        return if (intent.action == Intent.ACTION_VIEW) {
            intent.data?.getQueryParameter("id")
        } else {
            null
        }
    }
}

@Composable
fun EventDisplay(eventId: String?, modifier: Modifier = Modifier) {
    Column(
        modifier = modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "LinkOne App",
            fontSize = 24.sp,
            fontWeight = FontWeight.Bold,
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        if (eventId != null) {
            Text(
                text = "Event ID:",
                fontSize = 18.sp,
                modifier = Modifier.padding(bottom = 8.dp)
            )
            Text(
                text = eventId,
                fontSize = 20.sp,
                fontWeight = FontWeight.Medium
            )
        } else {
            Text(
                text = "No event ID found",
                fontSize = 16.sp
            )
        }
    }
}
```

* * *

## Preparing the URL

1. To associate the mobile app with the URL, we create and host an `assetlinks.json` file.

2. The requirements include:

  - It has to be publicly accessible and located at https://target-domain-name/.well-known/assetlinks.json
  - It must not be redirected (E.g., 301 or 302)
  - It has to be served as `application/json` type

3. The content must include 2 important values - the package name of the mobile app, and the SHA256 fingerprint of the signing cert.

```json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "xx.yy.zz",
      "sha256_cert_fingerprints": [
        "XX:XX:XX:XX..."
      ]
    }
  }
]
```

4. You can get the fingerprint using this command:

```bash
$ keytool -list -v -keystore your-cert
```

* * *

## Testing

1. Create a QR code of the URL you want to test. For example, https://calvin.my/test?id=111

2. Scan the QR code, and it opens up the default browser to the URL.

3. Build a signed copy of your mobile app and install it.

4. Scan the same QR code again, and it should now open the mobile app.

![](https://camy-pub.s3.ap-southeast-1.amazonaws.com/580e6636-0b0d-4f3e-9bff-9f45cf268c27.jpg)
