Deep Links

App Links, the soar:// scheme, and route resolution through the gate stack.

The template supports both a custom soar:// scheme and verified HTTPS App Links. All app-entry links pass through one resolver, then the outer nav either opens a modal destination or forwards the URI into the tab shell.

What it provides

SurfaceStatusImplementation
Custom schemeIntegratedAndroidManifest.xml accepts soar://...
Supabase callbackIntegratedsoar://auth-callback is reserved for supabase-kt
HTTPS App LinksIntegratedLINKING_DOMAIN manifest placeholder plus Android asset links
Cold and warm startsIntegratedMainActivity.onCreate() and onNewIntent() both call DeepLinkHandler
Refer share sheetExampleReferScreen shares the Play Store URL through ACTION_SEND

Key files

Entry channels

The manifest declares three relevant ACTION_VIEW filters:

LinkOwnerNotes
soar://auth-callbackSupabase AuthUsed for OTP, recovery, and OAuth callbacks; ignored by DeepLinkHandler
soar://home, soar://todos, etc.App routerCustom scheme links for app screens
https://<LINKING_DOMAIN>/homeApp routerVerified App Links after domain setup

Set the HTTPS host in local.properties:

LINKING_DOMAIN=example.com

Gradle injects it into the manifest as ${linkingDomain}. When the value is missing, the placeholder falls back to your.domain.example; custom-scheme links still work.

For HTTPS App Links, host an Asset Links file at:

https://<LINKING_DOMAIN>/.well-known/assetlinks.json

It must name the final application id and the SHA-256 certificate fingerprint for the signing certificate used by the installed build:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.soarstarter.kotlin",
      "sha256_cert_fingerprints": ["AA:BB:CC:..."]
    }
  }
]

Use the release upload or app-signing certificate for production links. A debug fingerprint only verifies debug builds.

Capture flow

ACTION_VIEW Intent
  -> MainActivity.onCreate() / onNewIntent()
  -> DeepLinkHandler.pendingUri
  -> DeepLinkResolver.resolve(uri, BuildConfig.LINKING_DOMAIN)
  -> AppNavGraph outer destinations or MainShellScreen tab destinations

DeepLinkHandler keeps a single pending URI in a StateFlow. After AppNavGraph reads it, it calls consume() so the same link is not handled twice.

DeepLinkResolver accepts:

  • soar://home
  • soar:///home
  • soar://anything?route=home
  • https://<LINKING_DOMAIN>/home

The route query parameter overrides the path, mirroring the Expo sibling's deep-link behavior.

Routable destinations

DeepLinkDestination currently contains:

URI segmentDestinationHandling
empty or homeHomeHome tab
component, componentsComponentComponent tab
showcaseShowcaseShowcase tab; removable leaf
todosTodosHome tab, then AppRoutes.Todos
meMeMe tab
profilePersonalInfoMe tab, then personal-info screen
notificationsNotificationsMe tab, then notifications screen
permissionsPermissionsMe tab, then permissions screen
referReferOuter nav destination
paywallPaywallOuter nav destination
redeem-codeRedeemCodeOuter nav destination

Refer, Paywall, and RedeemCode are handled by AppNavGraph before the URI reaches the shell. The rest are forwarded as shellDeepLinkUri and handled inside MainShellScreen.

Use adb against an installed build:

adb shell am start \
  -a android.intent.action.VIEW \
  -d "soar://notifications" \
  com.soarstarter.kotlin

Try the HTTPS form after Asset Links verification:

adb shell am start \
  -a android.intent.action.VIEW \
  -d "https://example.com/paywall" \
  com.soarstarter.kotlin

For push-tap testing, send a test push with:

{ "url": "soar://notifications" }

Refer screen tie-in

ReferScreen does not create a custom referral code. It builds a Play Store URL from the package name:

https://play.google.com/store/apps/details?id=<package-name>

Then it opens the Android share sheet with Intent.ACTION_SEND. Treat this as a starter pattern: replace getInviteLink() with your referral, branch, or campaign URL if invites are part of your product.

On this page