explain it from Android developer point of view: concept first, then manifest setup, flow, App Links, testing, and common interview points.

Deep Linking in Android 

1. What is Deep Linking?

Deep linking opens a specific screen inside an application instead of always opening its home screen.

Example URL:

https://shop.example.com/product/101

Expected result:

Open application → Product Details screen → Product 101
flowchart LR
    L["User taps link"] --> OS["Android resolves URL"]
    OS --> A["Opens application"]
    A --> P["Product Details"]

Deep links can come from:

* Website

* Email

* Social media

* Push notification

* QR code

* SMS

* Another application

Android: About deep links

---

2. Types of Deep Links

Android mainly supports:

1. Custom-scheme deep links

2. Web deep links

3. Verified Android App Links

TypeExampleVerification
Custom schemeshopapp://product/101No domain verification
Web deep linkhttps://shop.example.com/product/101Not necessarily verified
Android App Linkhttps://shop.example.com/product/101Domain and app are verified

---

3. Custom-Scheme Deep Link

Example:

shopapp://product/101

Manifest:

<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="shopapp"
        android:host="product" />
</intent-filter>

Limitation

Any application can register the same custom scheme.

flowchart TD
    L["shopapp://product/101"] --> A1["Your application"]
    L --> A2["Another application using same scheme"]
    A1 --> C["User may see chooser"]
    A2 --> C

Custom schemes are useful for controlled app-to-app communication, but HTTPS App Links are normally safer for URLs belonging to your website.

---

4. Web Deep Link

Example:

https://shop.example.com/product/101

A standard web deep link uses an HTTPS URL and a manifest intent filter.

However, without domain verification:

* Android may open the browser.

* Android may show an application chooser.

* Another application might also claim the URL.

For URLs owned by your organization, Android recommends verified App Links. [Android: Create deep links](https://developer.android.com/training/app-links/create-deeplinks)

---

5. Android App Links

An Android App Link is a verified HTTPS deep link.

Android verifies that:

* The application claims the website domain.

* The website authorizes the application.

* The signing certificate matches.

flowchart TD
    M["App manifest claims domain"] --> V["Android verification"]
    W["Website assetlinks.json<br/>authorizes app"] --> V
    V -->|Passed| O["Link opens app directly"]
    V -->|Failed| F["Browser or normal link handling"]

Benefits:

* Uses normal HTTPS URLs

* Opens the application directly after successful verification

* Reduces chooser dialogs

* Prevents an unrelated application from claiming your verified website

* Opens the website when the application is not installed

---

6. Manifest Configuration

<activity
    android:name=".MainActivity"
    android:exported="true">

    <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="http" />
        <data android:scheme="https" />
        <data android:host="shop.example.com" />

    </intent-filter>

</activity>

Important properties

PropertyPurpose
VIEWTells Android the Activity handles viewable data
DEFAULTAllows normal implicit Intent resolution
BROWSABLEAllows links from browsers and external sources
autoVerify="true"Requests domain verification
schemeUsually http and https for App Links
hostWebsite domain

Android: Add App Link intent filters

---

7. assetlinks.json

The website must authorize the Android application.

Create:

https://shop.example.com/.well-known/assetlinks.json

Example:

[
  {
    "relation": [
      "delegate_permission/common.handle_all_urls"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.shop",
      "sha256_cert_fingerprints": [
        "AA:BB:CC:DD:EE:FF:..."
      ]
    }
  }
]

It connects:

flowchart LR
    D["Website domain"] --> P["Application package"]
    P --> C["Signing certificate fingerprint"]

The file must:

* Be named assetlinks.json

* Be placed under /.well-known/

* Be publicly accessible through HTTPS

* Return valid JSON

* Avoid redirects

* Contain the correct package name

* Contain the correct SHA-256 signing fingerprint

Android: Configure website association

---

8. Play App Signing Fingerprint

When Play App Signing is enabled, the key used to sign the installed Play Store application can differ from the upload key.

Use the fingerprint from:

Play Console
→ Release
→ Setup
→ App integrity/App signing

A common mistake is adding only the local keystore or upload-key fingerprint.

For different builds, `assetlinks.json` can contain multiple fingerprints:
"sha256_cert_fingerprints": [
  "PRODUCTION_APP_SIGNING_FINGERPRINT",
  "DEBUG_SIGNING_FINGERPRINT"
]

Only include debug signing information on domains where that trust is intentionally allowed.

---

9. Internal Working

When a user clicks:

https://shop.example.com/product/101

Android performs the following steps:

sequenceDiagram
    actor U as User
    participant OS as Android
    participant W as Website
    participant A as Application
    participant N as Navigation

    U->>OS: Tap URL
    OS->>OS: Find matching intent filter
    OS->>W: Check domain association
    W-->>OS: assetlinks.json
    OS->>OS: Verify package and fingerprint
    OS->>A: Send ACTION_VIEW Intent
    A->>N: Parse and validate URI
    N->>N: Open product 101

The Activity receives an ACTION_VIEW Intent containing the URI.

val uri = intent?.data

val productId =
    uri?.pathSegments?.getOrNull(1)

Do not depend on a fixed index without verifying the path structure.

---

10. Deep Linking with Navigation Compose

Define a route:

@Serializable
data class ProductDetails(
    val productId: String
)

Configure the destination:

composable<ProductDetails>(
    deepLinks = listOf(
        navDeepLink<ProductDetails>(
            basePath =
                "https://shop.example.com/product"
        )
    )
) { backStackEntry ->

    val route =
        backStackEntry.toRoute<ProductDetails>()

    ProductDetailsScreen(
        productId = route.productId
    )
}

The manifest is still required so Android can deliver external links to the Activity.

flowchart LR
    URL["External URL"] --> IF["Manifest intent filter"]
    IF --> A["Activity"]
    A --> NH["NavHost"]
    NH --> D["Matching destination"]

---

11. Multiple Deep Links

Suppose the application supports:

https://shop.example.com/product/101
https://shop.example.com/category/mobile

Navigation destinations:

@Serializable
data class ProductDetails(
    val productId: String
)

@Serializable
data class Category(
    val categoryName: String
)
composable<ProductDetails>(
    deepLinks = listOf(
        navDeepLink<ProductDetails>(
            basePath =
                "https://shop.example.com/product"
        )
    )
) {
    // Product destination
}

composable<Category>(
    deepLinks = listOf(
        navDeepLink<Category>(
            basePath =
                "https://shop.example.com/category"
        )
    )
) {
    // Category destination
}

Route decision

flowchart TD
    U["Incoming URI"] --> P{"Path?"}
    P -->|product/id| PD["Product Details"]
    P -->|category/name| CL["Category List"]
    P -->|Unknown| H["Safe fallback or website"]

Keep a central route definition so the manifest, navigation graph, web team and backend notification team agree on URL formats.

---

12. Multiple Domains

Suppose the app handles:

https://shop.example.com/product/101
https://payment.example.com/status/5001

Use separate intent filters when URLs have different scheme/host/path combinations.

<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="http" />
    <data android:scheme="https" />
    <data android:host="shop.example.com" />
</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="http" />
    <data android:scheme="https" />
    <data android:host="payment.example.com" />
</intent-filter>

Each domain needs its own accessible assetlinks.json.

Important manifest rule

Different <data> elements inside one intent filter are combined. This can create URL combinations you did not intend.

Use separate filters for distinct URL patterns.

---

13. Deep Link with Login Check

A deep link may target a screen that requires authentication.

Example:

https://bank.example.com/payment/5001

If the user is logged out:

flowchart TD
    D["Deep link received"] --> V{"Link valid?"}
    V -->|No| H["Safe fallback"]
    V -->|Yes| L{"User logged in?"}
    L -->|Yes| T["Open target screen"]
    L -->|No| S["Store pending destination"]
    S --> LG["Open Login"]
    LG --> R{"Login successful?"}
    R -->|Yes| T
    R -->|No| C["Clear or safely retain request"]

Pending destination

sealed interface PendingDestination {
    data class Product(
        val productId: String
    ) : PendingDestination

    data class Payment(
        val transactionId: String
    ) : PendingDestination
}

Handling:

fun handleDeepLink(uri: Uri) {
    val destination = parseAndValidate(uri)
        ?: return openHome()

    if (sessionManager.isLoggedIn()) {
        navigate(destination)
    } else {
        pendingDestination = destination
        navigateToLogin()
    }
}

After successful login:

fun onLoginSuccess() {
    val destination = pendingDestination
    pendingDestination = null

    if (destination != null) {
        navigate(destination)
    } else {
        navigateToHome()
    }
}

Security rule

Do not treat possession of a deep link as authorization.

The user must still pass:

* Authentication

* Account ownership validation

* Role checks

* Transaction validation

* Step-up authentication when required

---

14. Cold Start vs Warm Start

Cold start

The application process is not running.

flowchart LR
    L["Tap link"] --> A["Create Activity"]
    A --> I["Read initial Intent"]
    I --> N["Navigate"]

Read the Intent during initial Activity creation.

Warm start

The Activity may already exist.

flowchart LR
    L["Tap another link"] --> O["onNewIntent"]
    O --> I["Process new Intent"]
    I --> N["Navigate"]

For manual handling:

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    handleDeepLink(intent)
}

Test both cold and warm cases. A common bug is supporting only the initial Intent.

Navigation libraries can handle much of this, but lifecycle and launch-mode behaviour should still be verified.

---

15. Push Notification Deep Link

A notification can include navigation data:

{
  "type": "product",
  "productId": "101"
}

The application converts trusted notification data into an internal destination or App Link.

flowchart LR
    N["Notification"] --> PI["PendingIntent"]
    PI --> A["Activity"]
    A --> V["Validate data"]
    V --> D["Destination"]

Use unique PendingIntent identities when different notifications must open different content.

Do not pass sensitive information such as tokens, OTPs or complete account details in the deep-link URL.

---

16. Security Considerations

Deep links are external input and must be treated as untrusted.

Validate:

* Scheme

* Host

* Path

* Argument type

* Argument length

* Allowed values

* Authentication state

* User authorization

* Redirect destination

Avoid open redirects

Dangerous URL:

https://example.com/open?url=https://evil.example

Do not blindly open arbitrary URLs.

Use an allowlist:

val allowedHosts = setOf(
    "shop.example.com",
    "support.example.com"
)

if (uri.host !in allowedHosts) {
    return
}

Never place sensitive data in URLs

Avoid:

https://example.com/login?token=SECRET_TOKEN

URLs can appear in:

* Browser history

* Logs

* Analytics

* Screenshots

* Referrer information

* Clipboard

Use a short-lived opaque identifier when necessary, then securely exchange it with the server.

---

17. Testing with ADB

Test a deep link:

adb shell am start \
  -W \
  -a android.intent.action.VIEW \
  -c android.intent.category.BROWSABLE \
  -d "https://shop.example.com/product/101" \
  com.example.shop

Test:

* App not running

* App running in background

* App already open

* User logged in

* User logged out

* Valid ID

* Invalid ID

* Missing argument

* Unknown path

* HTTP and HTTPS

* App installed and not installed

Android: Test App Links

---

18. Verify App Link Status

Reset current state:

adb shell pm set-app-links \
  --package com.example.shop 0 all

Force verification:

adb shell pm verify-app-links \
  --re-verify com.example.shop

Check result:

adb shell pm get-app-links \
  com.example.shop

Expected domain status:

shop.example.com: verified

Verification may be asynchronous, so wait before checking the result. Android: Verify App Links

---

19. Dynamic App Links

On supported Android 15+ devices, Dynamic App Links can refine URL rules through assetlinks.json.

They can control:

* Paths

* Query parameters

* Fragments

* Exclusions

This allows some rule updates without releasing a new app version.

flowchart LR
    M["Manifest defines allowed domain scope"] --> D["Dynamic rules refine it"]
    D --> P["Paths and parameters"]

Dynamic rules cannot expand beyond the URL scope permitted by the manifest. Older Android versions still depend on the manifest configuration.

---

20. Common Mistakes

Mistake 1: Using only a custom scheme

Another application may register the same scheme.

Prefer verified HTTPS App Links for organization-owned web URLs.

Mistake 2: Missing BROWSABLE

The browser cannot safely deliver the URL to the Activity.

Mistake 3: Missing android:exported="true"

An Activity handling external links must be accessible to the system.

Mistake 4: Incorrect signing fingerprint

With Play App Signing, use the production app-signing fingerprint, not only the upload-key fingerprint.

Mistake 5: Redirecting assetlinks.json

Serve it directly at:

https://domain/.well-known/assetlinks.json

Redirects can cause verification failure.

Mistake 6: Mixing unrelated <data> elements

The manifest combines elements in the same intent filter and may create unintended URL matches.

Use separate intent filters.

Mistake 7: Handling only cold start

Also handle a new deep link when the Activity/application is already running.

Mistake 8: Skipping login and authorization

A deep link chooses a destination; it does not give permission to access that destination.

Mistake 9: Passing complete objects

Pass a stable ID and load the latest data after authentication and authorization.

Mistake 10: Trusting URI parameters

Validate all input and safely reject malformed or unknown links.

---

21. Interview Questions and Answers

Q1. What is deep linking?

Deep linking opens a specific application screen or content using a URI instead of opening only the home screen.

Q2. What is the difference between a deep link and an App Link?

A deep link can use a custom scheme or web URL and may show a chooser. An App Link is a verified HTTP/HTTPS link associated with an owned website.

Q3. What does android:autoVerify="true" do?

It asks Android to verify the declared domain against the website’s assetlinks.json.

Q4. What is assetlinks.json?

It is a Digital Asset Links file hosted by the website that authorizes a package name and signing-certificate fingerprint to handle the website’s URLs.

Q5. Where is assetlinks.json hosted?

https://domain-name/.well-known/assetlinks.json

Q6. What happens if the app is not installed?

An HTTPS App Link can open the matching webpage instead.

Q7. How do you handle a protected deep link when the user is logged out?

Validate and store the intended destination, open Login, and navigate to the stored destination only after successful authentication and authorization.

Q8. What should be passed in a deep link?

Pass a minimal identifier such as a product ID or transaction reference. Do not pass tokens, OTPs or complete sensitive objects.

Q9. How do you handle multiple URLs?

Define separate routes and map validated host/path patterns to their correct destinations. Use separate manifest filters for distinct URL combinations.

Q10. How do you test a deep link?

Use ADB am start for routing, then use pm verify-app-links and pm get-app-links to check domain verification.

Q11. How do cold-start and warm-start deep links differ?

During a cold start, the Activity receives the initial Intent. During a warm start, an existing Activity may receive the new link through onNewIntent().

Q12. Are deep-link arguments secure?

No. They are external input and must be validated. Authentication and authorization must still be enforced.

---

Final Interview Answer

> Deep linking allows a URI to open specific content inside an Android application. For organization-owned web URLs, I prefer verified Android App Links. The Activity declares an HTTP/HTTPS intent filter with autoVerify, and the website hosts an assetlinks.json containing the package name and signing-certificate fingerprint. Android verifies this association and routes matching links to the application. Inside the app, I validate the URI, extract only safe identifiers and navigate to the appropriate screen. For protected content, I store the pending destination, complete login and authorization, and then continue. I test cold start, warm start, invalid input, multiple URLs and domain-verification status.