Skip to content
Tolinku
Tolinku
Sign In Start Free
Deep Linking · · 5 min read

Deep Linking and Android Instant Apps

By Tolinku Staff
|
Tolinku deep linking fundamentals dashboard screenshot for deep linking blog posts

Android Instant Apps (now called Google Play Instant) let users run your app from a URL without installing it. The user taps a link, a lightweight version of your app loads in seconds, and they interact with it immediately. If they like it, they can install the full app. The deep link URL that triggered the instant experience carries over.

This guide covers how to build Instant App experiences powered by deep links. For the iOS equivalent, see deep linking and App Clips. For Android App Links setup, see Android Instant Apps and deep linking.

The URL-to-Experience Flow

User taps link: https://yourapp.com/products/running-shoes
  → Android checks: is the full app installed?
    → Yes: open the full app via App Links
    → No: is Google Play Instant enabled for this URL?
      → Yes: load the Instant App module (~15MB)
        → Instant App opens to the product page
      → No: open the URL in Chrome

The same URL handles all three cases. App Links verification is required for all of them.

Instant App Module Structure

An Instant App is split into feature modules. Each module handles specific URLs:

app/
  ├── base/                    (shared code, always loaded)
  ├── feature-products/        (handles /products/* URLs, ~5MB)
  ├── feature-checkout/        (handles /checkout/* URLs, ~4MB)
  └── feature-account/         (handles /account/* URLs, ~3MB)

When a user taps a /products/running-shoes link, only the base and feature-products modules load. The checkout module loads when the user navigates to checkout.

Instant Apps require verified App Links. The assetlinks.json file must include the Instant App package:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourapp",
    "sha256_cert_fingerprints": [
      "YOUR_RELEASE_FINGERPRINT",
      "YOUR_INSTANT_APP_FINGERPRINT"
    ]
  }
}]

Manifest Configuration

Each feature module declares its URL patterns in the manifest:

<!-- feature-products/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dist="http://schemas.android.com/apk/distribution"
    package="com.yourapp.feature.products">

    <dist:module
        dist:instant="true"
        dist:title="Products">
        <dist:delivery>
            <dist:install-time />
        </dist:delivery>
    </dist:module>

    <application>
        <activity
            android:name=".ProductActivity"
            android:exported="true">
            <intent-filter android:autoVerify="true"
                android:order="1">
                <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"
                      android:host="yourapp.com"
                      android:pathPrefix="/products/" />
            </intent-filter>

            <meta-data
                android:name="default-url"
                android:value="https://yourapp.com/products" />
        </activity>
    </application>
</manifest>
class ProductActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val uri = intent.data ?: run {
            showDefaultProducts()
            return
        }

        val productSlug = uri.lastPathSegment
        val variant = uri.getQueryParameter("variant")
        val campaign = uri.getQueryParameter("utm_campaign")

        viewModel.loadProduct(productSlug, variant)

        // Track that this came from an instant app experience
        if (isInstantApp()) {
            analytics.trackInstantAppView(productSlug, campaign)
        }
    }

    private fun isInstantApp(): Boolean {
        return packageManager.isInstantApp
    }
}

Transitioning from Instant to Installed

Install Prompt

When the user wants to do something the Instant App cannot handle (e.g., create an account, access offline features), prompt them to install:

fun promptInstall() {
    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse("https://play.google.com/store/apps/details?id=com.yourapp")
        setPackage("com.android.vending")
        putExtra("callerId", packageName)
        putExtra("referrer", "utm_source=instant_app&utm_medium=install_prompt")
    }
    startActivity(intent)
}

Preserving State Through Install

Use the Cookie API to transfer data from the Instant App to the installed app:

import com.google.android.gms.instantapps.PackageManagerCompat

// Instant App: save state before install
fun saveStateForInstall(userId: String, cartItems: List<CartItem>) {
    val cookie = JSONObject().apply {
        put("userId", userId)
        put("cart", JSONArray(cartItems.map { it.toJSON() }))
        put("lastViewedProduct", currentProductSlug)
    }.toString().toByteArray()

    PackageManagerCompat.setInstantAppCookie(this, cookie)
}

// Installed app: restore state after install
fun restoreFromInstantApp() {
    val cookie = PackageManagerCompat.getInstantAppCookie(this)
    if (cookie.isNotEmpty()) {
        val state = JSONObject(String(cookie))
        val userId = state.optString("userId")
        val lastProduct = state.optString("lastViewedProduct")

        if (userId.isNotEmpty()) {
            authManager.restoreSession(userId)
        }
        if (lastProduct.isNotEmpty()) {
            navigateToProduct(lastProduct)
        }

        // Clear the cookie after restoring
        PackageManagerCompat.setInstantAppCookie(this, ByteArray(0))
    }
}

After installation, the same URLs that triggered the Instant App now open the installed app. No URL changes needed. The App Links verification covers both instant and installed versions.

Size Optimization

The 15MB Limit

Each Instant App module must be under 15MB (including the base module). Strategies to stay within the limit:

  1. Move large assets to a CDN. Download images, 3D models, and videos on demand.
  2. Use dynamic feature modules. Split features so only the needed code loads.
  3. Minimize dependencies. Each library adds to the module size.
  4. Use WebP images. Smaller than PNG/JPEG for the same quality.
  5. Enable ProGuard/R8. Remove unused code and resources.
// build.gradle - enable code shrinking
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
        }
    }
}

Feature Module Boundaries

Design your modules around URL patterns:

Module URLs Size Budget
base (always loaded) ~5MB
products /products/* ~4MB
search /search* ~3MB
checkout /checkout/* ~3MB

Instant Apps and Attribution

UTM Parameters

UTM parameters in the deep link URL pass through to the Instant App:

https://yourapp.com/products/shoes?utm_source=email&utm_campaign=summer&utm_medium=link

Track these in the Instant App to measure which channels drive instant app experiences.

Install Referrer

When the user installs from the Instant App, the install referrer can carry context:

// Include referrer when prompting install
val referrer = "utm_source=instant_app&utm_content=${currentProductSlug}"
val playStoreUri = Uri.parse(
    "https://play.google.com/store/apps/details?id=com.yourapp&referrer=$referrer"
)

Tolinku and Instant Apps

Tolinku handles App Links verification for both installed and Instant App versions. Configure your routes in the Tolinku dashboard, and the same assetlinks.json serves both app modes. The web fallback page shows for users on non-Android devices or when the Instant App module is not available.

For more on deep linking trends, see the future of mobile deep linking. For App Clips (the iOS equivalent), see deep linking and App Clips.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.