# How to Set Up Android App Links in Flutter > Implement Android App Links in Flutter with AndroidManifest.xml config, assetlinks.json setup, Dart code, and SHA-256 fingerprint generation. - Canonical: https://redirectly.app/blog/flutter-android-app-links-setup - Site: [Redirectly](https://redirectly.app) โ€” deferred deep linking for Flutter & React Native - Published: 2024-02-15 - Author: Redirectly - Category: Flutter ยท 8 min read Master Android App Links with this comprehensive guide. Learn AndroidManifest.xml configuration, assetlinks.json setup, SHA-256 generation, and Dart implementation. ## Table of Contents 1. What are Android App Links? 2. AndroidManifest.xml Configuration 3. Generate SHA-256 Fingerprints 4. Create assetlinks.json File 5. Dart Code Implementation 6. Testing App Links 7. Common Issues & Fixes ## What are Android App Links? **Definition** Android App Links are a special type of deep link that use standard HTTPS URLs to launch your app directly without showing the app chooser dialog. They verify the link belongs to the app domain using the assetlinks.json file hosted on your server. ### Key Advantages - No app chooser dialog - Direct app launching - Domain verification - Seamless UX - Better engagement - Works with Chrome/browsers ### How They Work - User clicks an HTTPS link - Android checks intent filters - Android validates assetlinks.json - App opens if verified - Domain matches SHA-256 - No fallback dialog shown ## AndroidManifest.xml Configuration ### Step 1: Add Intent Filters Update your MainActivity in AndroidManifest.xml to handle App Links: ```xml ``` **Important:** The `android:autoVerify="true"` attribute tells Android to verify this is a valid App Link. ### Step 2: Configure launchMode Set launchMode to singleTask to prevent multiple instances when app links are opened: ```xml android:launchMode="singleTask" ``` ## Generate SHA-256 Fingerprints ### What is SHA-256? A SHA-256 fingerprint is a cryptographic hash of your app's signing certificate. Android uses it to verify that assetlinks.json matches your app. ### Step 1: Generate Debug SHA-256 Use keytool to get the SHA-256 of your debug keystore: ```bash keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android ``` On macOS/Linux. For Windows, replace `~/.android` with your keystore path. ### Step 2: Generate Release SHA-256 For your release keystore (replace paths with your keystore file): ```bash keytool -list -v -keystore /path/to/your/keystore.jks -alias your_key_alias -storepass your_store_password -keypass your_key_password ``` ### Output Format Look for the line starting with "SHA256:". It will look like: `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` ## Create assetlinks.json File ### What is assetlinks.json? The assetlinks.json file proves you own both the domain and the app. It must be hosted at `/.well-known/assetlinks.json` on your domain. ### Step 1: Create assetlinks.json Create the JSON file with your app's package name and SHA-256 fingerprints: ```json [ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp", "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" ] } } ] ``` ### Step 2: Multiple Fingerprints (Debug & Release) Include both debug and release SHA-256 fingerprints if you test with both: ```json [ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp", "sha256_cert_fingerprints": [ "DEBUG: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:AA:BB", "RELEASE: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" ] } } ] ``` ### Step 3: Host on Your Server Upload the file to your domain's .well-known directory: ```text https://yourdomain.com/.well-known/assetlinks.json ``` **Tip:** Validate your assetlinks.json using our [assetlinks Validator](https://redirectly.app/assetlinks-validator.md) tool. ## Dart Code Implementation ### Step 1: Add uni_links Package Add the uni_links package to your pubspec.yaml: ```yaml dependencies: flutter: sdk: flutter uni_links: ^0.9.0 ``` ### Step 2: Create App Link Handler Implement the handler for incoming App Links: ```dart import 'package:flutter/material.dart'; import 'package:uni_links/uni_links.dart'; import 'dart:async'; class AppLinkHandler { static StreamSubscription? _deepLinkSubscription; static Future initialize(BuildContext context) async { // Handle link when app is opened from terminated state final String? initialLink = await getInitialLink(); if (initialLink != null) { _handleAppLink(initialLink, context); } // Listen for app links when app is running _deepLinkSubscription = linkStream.listen( (String link) { _handleAppLink(link, context); }, onError: (err) { debugPrint('App link error: $err'); }, ); } static void _handleAppLink(String link, BuildContext context) { final Uri uri = Uri.parse(link); debugPrint('Received app link: $link'); debugPrint('Scheme: ${uri.scheme}'); debugPrint('Host: ${uri.host}'); debugPrint('Path: ${uri.path}'); debugPrint('Query parameters: ${uri.queryParameters}'); // Route based on path segments if (uri.pathSegments.isNotEmpty) { final String firstSegment = uri.pathSegments[0]; switch (firstSegment) { case 'product': if (uri.pathSegments.length > 1) { final String productId = uri.pathSegments[1]; _navigateToProduct(context, productId); } break; case 'user': if (uri.pathSegments.length > 1) { final String userId = uri.pathSegments[1]; _navigateToUser(context, userId); } break; case 'share': if (uri.pathSegments.length > 1) { final String referralCode = uri.pathSegments[1]; _handleReferral(context, referralCode); } break; default: _navigateToHome(context); } } else { _navigateToHome(context); } } static void _navigateToProduct(BuildContext context, String productId) { debugPrint('Navigate to product: $productId'); // Implementation: Navigate to product screen } static void _navigateToUser(BuildContext context, String userId) { debugPrint('Navigate to user: $userId'); // Implementation: Navigate to user profile screen } static void _handleReferral(BuildContext context, String referralCode) { debugPrint('Handle referral: $referralCode'); // Implementation: Handle referral code } static void _navigateToHome(BuildContext context) { debugPrint('Navigate to home'); // Implementation: Navigate to home screen } static void dispose() { _deepLinkSubscription?.cancel(); } } ``` ### Step 3: Initialize in main.dart Initialize the handler in your main app widget: ```dart import 'package:flutter/material.dart'; import 'app_link_handler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { AppLinkHandler.initialize(context); }); } @override void dispose() { AppLinkHandler.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', home: const HomeScreen(), ); } } ``` ## Testing App Links ### Test with adb (Android Debug Bridge) Use ADB to simulate app link clicks: ```bash adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.com/product/123" com.example.myapp ``` ### Manual Testing on Device Test app links manually on your Android device: 1. Open Chrome or another browser on your device 2. Navigate to your app link URL: `https://yourdomain.com/product/123` 3. App should open directly without app chooser dialog 4. Verify correct screen/content is displayed ### Verify assetlinks.json is Live Check if your assetlinks.json is accessible: ```bash curl -I https://yourdomain.com/.well-known/assetlinks.json ``` Expected response: `HTTP/1.1 200 OK` ## Common Issues & Fixes ### Issue: assetlinks.json Not Found (404) **Cause:** File not uploaded or wrong path **Solution:** - Verify file is at `/.well-known/assetlinks.json` - Check file is properly uploaded to server - Test with curl: `curl https://yourdomain.com/.well-known/assetlinks.json` - Validate using our [validator](https://redirectly.app/assetlinks-validator.md) ### Issue: SHA-256 Fingerprint Mismatch **Cause:** Wrong SHA-256 in assetlinks.json **Solution:** - Regenerate SHA-256 using keytool - Ensure fingerprint matches your signing certificate - Check for typos in the hash - Include both debug and release hashes if testing both ### Issue: Deep Link Opens Browser Instead of App **Cause:** Intent filter or autoVerify configuration missing **Solution:** - Verify `android:autoVerify="true"` is set - Check intent filter has correct data entries - Rebuild and reinstall app - Wait a few seconds after install before testing ### Issue: App Not Receiving Deep Link **Cause:** Handler not initialized or context issue **Solution:** - Initialize handler after widget tree is built - Use WidgetsBinding.addPostFrameCallback() - Check debug output for handler logs - Ensure uni_links is in pubspec.yaml ## Master Deep Linking with go_router Now that you have App Links working, learn how to use go_router for advanced deep linking with route guards and deferred links. - [go_router Deep Linking](https://redirectly.app/blog/flutter-deep-linking-go-router.md) - [Deferred Deep Linking](https://redirectly.app/flutter-deferred-deep-linking.md) ## Related Articles - [iOS Universal Links Setup](https://redirectly.app/blog/flutter-universal-links-ios-setup.md) โ€” Learn how to configure Universal Links for iOS with Xcode, AASA file, and implementation. - [Deep Linking with go_router](https://redirectly.app/blog/flutter-deep-linking-go-router.md) โ€” Master go_router for advanced deep linking with route guards and deferred link handling. --- Full site index for AI agents: https://redirectly.app/llms.txt