Deep links universal links
Skill almasumdev/awesome-mobile-agent-skills/.github/skills/lifecycle/deep-links-universal-links
Stack-agnostic agent skills and workflows shared across iOS, Android, Flutter, React Native, and KMP.
npx -y skills add almasumdev/awesome-mobile-agent-skills --skill deep-links-universal-linksAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Implementing and testing deep links on mobile - Android App Links, iOS Universal Links, custom URL schemes, attribution providers, and sunset guidance for Firebase Dynamic Links. Use when adding, debugging, or migrating deep link behavior.
SKILL.md
6.6 KB, as published. Nobody here has run it
Deep Links and Universal Links
Instructions
Deep links are the front door of a mobile app. They power marketing, referrals, support links, push notifications, and share flows. Get them wrong and none of the growth loops work.
1. Link Types
| Type | iOS | Android | Properties |
|---|---|---|---|
| Custom scheme | myapp:// | myapp:// | Works without verification, not shareable on the web |
| Universal/App Link | https://app.example.com/... | https://app.example.com/... | Verified domain, opens app if installed, falls back to web |
| Deferred deep link | 3rd-party SDK | 3rd-party SDK | Carries context through install from store |
| Firebase Dynamic Links | deprecated | deprecated | Sunset Aug 25, 2025. Do not build new integrations. |
| Branch / Adjust / AppsFlyer | Supported | Supported | Replacement for FDL, attribution included |
2. iOS Universal Links
- Host an
apple-app-site-association(AASA) JSON file athttps://example.com/.well-known/apple-app-site-association, served over HTTPS, no redirects, content-typeapplication/json. - Add the
Associated Domainsentitlement withapplinks:example.com. - Handle the link in
SceneDelegate/ SwiftUI.
{
"applinks": {
"details": [{
"appIDs": ["TEAMID.com.example.app"],
"components": [
{ "/": "/article/*" },
{ "/": "/u/*" }
]
}]
}
}
// SwiftUI
ContentView()
.onOpenURL { url in DeepLinkRouter.shared.handle(url) }
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
if let url = activity.webpageURL { DeepLinkRouter.shared.handle(url) }
}
3. Android App Links
- Declare an intent filter with
android:autoVerify="true". - Host
assetlinks.jsonathttps://example.com/.well-known/assetlinks.json. - Verify with
adb shell pm get-app-links com.example.app.
<!-- AndroidManifest.xml -->
<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="https" android:host="app.example.com" />
<data android:pathPrefix="/article/" />
</intent-filter>
</activity>
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": ["AB:CD:..."]
}
}]
4. Cross-Platform Handling
// Flutter via go_router
final router = GoRouter(routes: [
GoRoute(path: '/article/:id', builder: (_, s) => ArticleScreen(id: s.pathParameters['id']!)),
]);
// plus app_links plugin to catch initial and streamed URIs
// React Native with Expo Linking
Linking.getInitialURL().then((url) => url && router.handle(url));
Linking.addEventListener('url', ({ url }) => router.handle(url));
5. Attribution and Deferred Deep Links
If a link must carry context through a fresh install from the store, the platforms alone do not solve it. Options:
- Branch, AppsFlyer OneLink, Adjust deep links -- full attribution and deferred deep linking.
- Build your own with a clickthrough page that writes a short-lived token to a server and reads it on first launch (by IP+UA fingerprint or a one-time code flow).
Firebase Dynamic Links is sunsetted (August 25, 2025). Migrate to one of the above or roll your own.
6. Routing Contract
All deep links must resolve through a single router:
- Parse the URL into a typed route.
- Reject unknown paths gracefully (land on a sensible screen, log a metric).
- Require auth where appropriate; store the target URL and replay after sign-in.
- Establish a back stack so the system back returns to a reasonable place.
sealed class Route {
data class Article(val id: String) : Route()
data class Profile(val handle: String) : Route()
data object Unknown : Route()
}
fun parse(uri: Uri): Route = when {
uri.pathSegments.firstOrNull() == "article" && uri.pathSegments.size == 2 ->
Route.Article(uri.pathSegments[1])
uri.pathSegments.firstOrNull() == "u" && uri.pathSegments.size == 2 ->
Route.Profile(uri.pathSegments[1])
else -> Route.Unknown
}
7. Testing
- iOS:
xcrun simctl openurl booted https://app.example.com/article/42. - Android:
adb shell am start -W -a android.intent.action.VIEW -d "https://app.example.com/article/42" com.example.app. - Custom scheme iOS:
xcrun simctl openurl booted myapp://article/42. - Custom scheme Android:
adb shell am start -a android.intent.action.VIEW -d "myapp://article/42". - Always test both cold start and warm start paths. They behave differently.
8. Common Bugs
- AASA not served with the right content-type or blocked by redirects. Verify with
curl -I. autoVerifyfailing due to missingassetlinks.jsonfor the release keystore. Both debug and release SHA-256s must be present.- Links opening the browser instead of the app because the user explicitly disassociated the domain. Provide a manual "open in app" fallback.
- Universal Links failing inside apps that use in-app browsers (WKWebView, SFSafariViewController navigations). Tap-through from Safari to the app is fine; programmatic opens often are not.
- Links arriving before the router is mounted on cold start. Buffer and replay when the navigator is ready.
9. Anti-Patterns
- Using custom schemes for marketing links (unshareable, unverified).
- Embedding user secrets in link paths or query strings.
- Relying on Firebase Dynamic Links for new projects.
- Not versioning the link namespace. Reserve a path prefix like
/l/v1/so breaking changes are possible.
Checklist
- AASA and
assetlinks.jsonare hosted correctly and verified in CI. - Associated Domains entitlement and App Links intent filter are present.
- A single router parses and dispatches every URL.
- Unknown links degrade gracefully.
- Cold-start links are buffered and replayed after navigator is ready.
- Auth-gated routes store and replay the target URL.
- Attribution/deferred deep linking uses a supported provider (not Firebase Dynamic Links).
- Both debug and release signing SHA-256s are in
assetlinks.json. - Manual QA steps (
simctl,adb) are documented.