Android App Links let your Flutter app handle HTTPS links without showing a disambiguation dialog. When a user taps a link to your domain, the app opens directly. This guide covers the Android-specific configuration for App Links in Flutter apps.
For the full Flutter deep linking setup (including iOS), see Flutter Deep Linking: Complete Setup Tutorial. For go_router integration, see Flutter go_router Deep Links. For the Android fundamentals, see Android App Links: Complete Implementation Guide.
How App Links Differ from Deep Links
Android has two types of link handling:
Deep Links (basic): Use intent-filter without autoVerify. When the user taps a matching link, Android shows a dialog asking which app should open it. Other apps can also claim the same URLs.
App Links (verified): Use intent-filter with android:autoVerify="true". Android verifies that you own the domain by checking /.well-known/assetlinks.json. Once verified, your app opens directly with no dialog.
Always use App Links for production. Deep Links without verification are only appropriate for development and testing.
Step 1: Configure AndroidManifest.xml
Open android/app/src/main/AndroidManifest.xml and add intent filters to your main activity:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Flutter deep linking metadata -->
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="true" />
<!-- Default Flutter intent filter -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- App Links (verified) -->
<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="go.yourapp.com" />
</intent-filter>
</activity>
Key Attributes
android:autoVerify="true": Triggers Android's domain verification process. Without this, links show a disambiguation dialog.android:launchMode="singleTop": Reuses the existing activity when a link is tapped while the app is already running. Without this, a new activity is created, and the deep link may not reach your Dart code.flutter_deeplinking_enabled: Tells Flutter's engine to forward incoming URLs to your Dart router automatically.
Multiple Domains
If your links come from multiple domains:
<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="go.yourapp.com" />
<data android:scheme="https" android:host="links.yourapp.com" />
</intent-filter>
Note: each domain you add requires its own assetlinks.json file.
Path Restrictions
To handle only specific paths (not all URLs on the domain):
<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="go.yourapp.com"
android:pathPrefix="/product" />
<data
android:scheme="https"
android:host="go.yourapp.com"
android:pathPrefix="/invite" />
</intent-filter>
Available path matching options:
android:path="/exact/path"– Exact matchandroid:pathPrefix="/prefix"– Starts withandroid:pathPattern="/product/.*"– Regex pattern
Step 2: Digital Asset Links (assetlinks.json)
Your domain must serve a verification file at:
https://go.yourapp.com/.well-known/assetlinks.json
File Content
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}]
If using Tolinku, the assetlinks.json is generated automatically when you configure your Appspace with your package name and SHA-256 fingerprints.
Getting Your SHA-256 Fingerprint
Debug keystore:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android
Release keystore:
keytool -list -v -keystore /path/to/your/release.keystore -alias your-alias
Google Play App Signing: If you use Google Play's app signing feature (recommended), Google re-signs your APK with their own key. You need both:
- Your upload key fingerprint (from your keystore)
- Google's app signing key fingerprint (from Play Console: Setup > App signing > App signing key certificate)
Include both in your assetlinks.json:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourapp",
"sha256_cert_fingerprints": [
"YOUR:UPLOAD:KEY:FINGERPRINT",
"GOOGLE:SIGNING:KEY:FINGERPRINT"
]
}
}]
Verification
Check that the file is accessible and valid:
# Accessibility
curl -I https://go.yourapp.com/.well-known/assetlinks.json
# Content
curl https://go.yourapp.com/.well-known/assetlinks.json | python3 -m json.tool
Use Google's Digital Asset Links tool to validate the file against your app.
Step 3: Handling Links in Dart
With go_router (Recommended)
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
return ProductScreen(id: state.pathParameters['id']!);
},
),
GoRoute(
path: '/invite/:code',
builder: (context, state) {
return InviteScreen(code: state.pathParameters['code']!);
},
),
],
errorBuilder: (context, state) => const NotFoundScreen(),
);
With flutter_deeplinking_enabled set to true, the URL path is automatically forwarded to go_router.
With app_links Package (For Full URL Access)
If you need access to the full URL (including the host), use the app_links package:
import 'package:app_links/app_links.dart';
class DeepLinkService {
final AppLinks _appLinks = AppLinks();
void init() {
_appLinks.uriLinkStream.listen((Uri uri) {
handleLink(uri);
});
_appLinks.getInitialLink().then((Uri? uri) {
if (uri != null) handleLink(uri);
});
}
void handleLink(Uri uri) {
// Access full URL: uri.host, uri.path, uri.queryParameters
router.go(uri.path);
}
}
Step 4: Verify App Links
On-Device Verification
After installing your app, check that Android has verified the domain:
# Check verification status
adb shell pm get-app-links com.yourapp
The output shows the verification status for each domain:
com.yourapp:
ID: ...
Signatures: [...]
Domain verification state:
go.yourapp.com: verified
If it shows none or legacy_failure, the verification failed. Common causes:
- The assetlinks.json is not accessible
- The SHA-256 fingerprint doesn't match
- The package name doesn't match
Force Re-Verification
# Reset verification state
adb shell pm set-app-links --package com.yourapp 0 all
# Trigger re-verification
adb shell pm verify-app-links --re-verify com.yourapp
Test a Link
adb shell am start -a android.intent.action.VIEW \
-d "https://go.yourapp.com/product/123" \
com.yourapp
If the app opens directly (no disambiguation dialog), App Links are working.
Troubleshooting
Disambiguation Dialog Still Appears
- Verify
android:autoVerify="true"is on the intent filter - Check that assetlinks.json is accessible over HTTPS without redirects
- Confirm the SHA-256 fingerprint matches (especially the Google Play App Signing key)
- Try resetting and re-verifying via ADB
Links Open in Browser
- The app may not be set as the default handler. Go to Settings > Apps > Your App > Open by default > Supported links, and enable it.
- Some Android manufacturers have custom link handling that ignores App Links. Samsung and Xiaomi devices are known to have quirks.
Links Work in ADB but Not from Browser
- Chrome handles App Links differently than ADB. Verify the intent filter is correctly configured.
- Some browsers intercept links instead of delegating to the OS. Test from multiple apps (Gmail, Messages, etc.).
Debug vs Release Fingerprint Mismatch
The most common issue: you test with a debug build (which uses the debug keystore) but your assetlinks.json only has the release fingerprint. Include both fingerprints during development.
Production Checklist
- Intent filter with
autoVerify="true"in AndroidManifest.xml -
flutter_deeplinking_enabledmetadata set totrue -
launchMode="singleTop"on the activity - assetlinks.json accessible at
/.well-known/assetlinks.json - SHA-256 fingerprint includes release key
- SHA-256 fingerprint includes Google Play App Signing key (if applicable)
- Domain verification status is “verified” via
adb shell pm get-app-links - Links open without disambiguation dialog on physical device
- Cold start and warm start links both work
- go_router routes match all deep link paths
For the cross-platform perspective, see Cross-Platform Deep Linking Guide. For the Tolinku Flutter SDK, see the Flutter SDK docs.
Get deep linking tips in your inbox
One email per week. No spam.