MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Deep links connect external content to specific screens in your app. Web links, push notifications, and app shortcuts all use them. Here's how to implement, test, and debug Android deep links correctly.
On this page
Deep links connect the outside world — websites, notifications, QR codes, emails — to specific screens in your app. Implementing them incorrectly means users land on the wrong screen, or the app crashes because it wasn't expecting that navigation state.
Here's how to do it right.
Explicit deep links: Created by your app and shared via notification, widget, or shortcut. App must be installed.
val deepLink = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.taskDetailFragment)
.setArguments(bundleOf("taskId" to "task-123"))
.createPendingIntent()Implicit deep links: URLs that can be handled by your app. These require
<intent-filter>App Links (HTTP/HTTPS): Verified deep links that open exclusively in your app (no app chooser dialog). Require domain verification.
In
AndroidManifest.xml<activity android:name=".MainActivity" android:exported="true">
<!-- Your main intent filter -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep link 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="https"
android:host="app.yourdomain.com"
android:pathPrefix="/tasks" />
</intent-filter>
</activity>android:autoVerify="true"// Define deep link in your nav graph
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable(
route = "tasks/{taskId}",
arguments = listOf(navArgument("taskId") { type = NavType.StringType }),
deepLinks = listOf(
navDeepLink { uriPattern = "https://app.yourdomain.com/tasks/{taskId}" }
)
) { backStackEntry ->
val taskId = backStackEntry.arguments?.getString("taskId")
TaskDetailScreen(taskId = taskId)
}
}Handle the incoming intent in
MainActivityclass MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
// Handle the deep link intent
LaunchedEffect(Unit) {
intent?.data?.let { uri ->
navController.handleDeepLink(intent)
}
}
AppNavHost(navController = navController)
}
}
// Handle deep links when app is already running
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// navController should handle this automatically with proper nav setup
setIntent(intent)
}
}For verified App Links (no app chooser), add an
assetlinks.json// https://yourdomain.com/.well-known/assetlinks.json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourapp.package",
"sha256_cert_fingerprints": [
"AB:CD:EF:12:34:56:..." // Your release keystore SHA-256
]
}
}]Get your keystore's SHA-256:
keytool -list -v -keystore release.jks -alias yourapp -storepass "YOUR_STORE_PASS" | grep SHA256Verify your setup at:
https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourdomain.com&relation=delegate_permission/common.handle_all_urlsVia ADB:
# Test while app is not running
adb shell am start -W -a android.intent.action.VIEW \
-d "https://app.yourdomain.com/tasks/task-123" \
com.yourapp.package
# Test with custom scheme
adb shell am start -W -a android.intent.action.VIEW \
-d "yourapp://tasks/task-123"Via Android Studio:
Run → Edit Configurations → Deep Link tab → enter URL
Not handling null in deep link arguments. If the deep link URL is malformed or the expected parameter is missing, your screen must handle
nullcomposable("tasks/{taskId}") { backStackEntry ->
val taskId = backStackEntry.arguments?.getString("taskId")
if (taskId == null) {
// Redirect to home, not crash
LaunchedEffect(Unit) { navController.navigate("home") }
return@composable
}
TaskDetailScreen(taskId = taskId)
}Not handling back stack correctly. When a user opens a deep link to a task detail screen, pressing back should go to the task list — not exit the app. Configure the back stack in your nav graph:
navDeepLink {
uriPattern = "https://app.yourdomain.com/tasks/{taskId}"
}
// Ensure "home" is in the back stack before "tasks/{taskId}"Domain verification failing. Check that:
assetlinks.jsonapplication/jsonandroid:autoVerify="true"Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read