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
| Surface | Status | Implementation |
|---|---|---|
| Custom scheme | Integrated | AndroidManifest.xml accepts soar://... |
| Supabase callback | Integrated | soar://auth-callback is reserved for supabase-kt |
| HTTPS App Links | Integrated | LINKING_DOMAIN manifest placeholder plus Android asset links |
| Cold and warm starts | Integrated | MainActivity.onCreate() and onNewIntent() both call DeepLinkHandler |
| Refer share sheet | Example | ReferScreen shares the Play Store URL through ACTION_SEND |
Key files
Entry channels
The manifest declares three relevant ACTION_VIEW filters:
| Link | Owner | Notes |
|---|---|---|
soar://auth-callback | Supabase Auth | Used for OTP, recovery, and OAuth callbacks; ignored by DeepLinkHandler |
soar://home, soar://todos, etc. | App router | Custom scheme links for app screens |
https://<LINKING_DOMAIN>/home | App router | Verified App Links after domain setup |
Set the HTTPS host in local.properties:
LINKING_DOMAIN=example.comGradle 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.
Android App Links verification
For HTTPS App Links, host an Asset Links file at:
https://<LINKING_DOMAIN>/.well-known/assetlinks.jsonIt 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 destinationsDeepLinkHandler 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://homesoar:///homesoar://anything?route=homehttps://<LINKING_DOMAIN>/home
The route query parameter overrides the path, mirroring the Expo sibling's
deep-link behavior.
Routable destinations
DeepLinkDestination currently contains:
| URI segment | Destination | Handling |
|---|---|---|
empty or home | Home | Home tab |
component, components | Component | Component tab |
showcase | Showcase | Showcase tab; removable leaf |
todos | Todos | Home tab, then AppRoutes.Todos |
me | Me | Me tab |
profile | PersonalInfo | Me tab, then personal-info screen |
notifications | Notifications | Me tab, then notifications screen |
permissions | Permissions | Me tab, then permissions screen |
refer | Refer | Outer nav destination |
paywall | Paywall | Outer nav destination |
redeem-code | RedeemCode | Outer 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.
Test links
Use adb against an installed build:
adb shell am start \
-a android.intent.action.VIEW \
-d "soar://notifications" \
com.soarstarter.kotlinTry the HTTPS form after Asset Links verification:
adb shell am start \
-a android.intent.action.VIEW \
-d "https://example.com/paywall" \
com.soarstarter.kotlinFor 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.
