Deep linking is a crucial feature in modern mobile applications, allowing users to navigate directly to specific content within an app from external sources such as web pages, email links, or social media posts. Properly handling different types of deep links ensures a seamless user experience and can significantly boost user engagement. In Flutter, handling deep links involves configuring your app to respond to specific URI schemes and routes, enabling navigation to the intended destination.
What is Deep Linking?
Deep linking refers to the mechanism of using a uniform resource identifier (URI) that links to a specific location within a mobile app rather than simply launching the app’s home screen. Deep links can be categorized into:
- Custom Scheme Deep Links: Utilize a custom URI scheme (e.g.,
myapp://) to open the app and navigate to a specific location. - Universal Links (Android App Links and iOS Universal Links): Standard web links (
https://) that also open the app directly if it is installed, otherwise, they open in a web browser.
Why Handle Different Types of Deep Links?
- Enhanced User Experience: Provides seamless navigation to relevant content.
- Increased User Engagement: Drives users directly to specific features, reducing friction.
- Better App Promotion: Enables precise campaign tracking and personalized experiences.
How to Handle Different Types of Deep Links in Flutter
Handling deep links in Flutter involves setting up both custom scheme and universal links to cater to a wider range of use cases.
Step 1: Set Up Custom Scheme Deep Links
Custom scheme deep links require configuring your Flutter app to respond to a specific URI scheme.
1.1. Android Configuration
In your AndroidManifest.xml file (located in android/app/src/main/), add an intent filter to the activity that should handle the deep link:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"> <!-- Add this line -->
<!-- ... other configurations ... -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="open" /> <!-- Your custom scheme and host -->
</intent-filter>
</activity>
Explanation:
android:launchMode="singleTask"ensures that only one instance of the activity is created.<data android:scheme="myapp" android:host="open" />specifies that this activity can handle URIs with the schememyappand hostopen(e.g.,myapp://open?param1=value1).
1.2. iOS Configuration
In your Info.plist file (located in ios/Runner/), add a CFBundleURLTypes array to define your custom URL scheme:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.example.myapp</string> <!-- Bundle identifier -->
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string> <!-- Your custom scheme -->
</array>
</dict>
</array>
Explanation:
CFBundleURLNameis a unique identifier for your app.CFBundleURLSchemesis an array that lists the custom URL schemes your app can handle.
Step 2: Implement Universal Links
Universal links provide a more secure and standardized way to handle deep links using HTTPS.
2.1. Android App Links
To set up Android App Links, you need to:
- Associate Your App with Your Website:
- Update
AndroidManifest.xml: android:autoVerify="true"enables automatic verification of the App Links.<data android:scheme="https" android:host="www.example.com" />specifies that this activity can handle HTTPS URLs from your domain.
Create a assetlinks.json file and host it at /.well-known/assetlinks.json on your domain. This file verifies that your website is associated with your Android app.
Example assetlinks.json file:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp", <!-- Your app package name -->
"sha256_cert_fingerprints": ["YOUR_SHA256_CERT_FINGERPRINT"]
}
}
]
To generate the SHA256 certificate fingerprint, use the following command:
keytool -list -v -keystore mystore.jks -alias myalias
Add an intent filter to your activity to handle HTTPS URLs, and enable auto-verification:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:autoVerify="true"> <!-- Add autoVerify -->
<!-- ... other configurations ... -->
<intent-filter android:autoVerify="true"> <!-- Ensure autoVerify is set to 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="www.example.com" /> <!-- Your domain -->
</intent-filter>
</activity>
Explanation:
2.2. iOS Universal Links
To set up iOS Universal Links:
- Associate Your App with Your Website:
- Enable Associated Domains Entitlement:
Create an apple-app-site-association file and host it at /.well-known/apple-app-site-association on your domain. This file verifies that your website is associated with your iOS app.
Example apple-app-site-association file:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "YOUR_TEAM_ID.com.example.myapp", <!-- Your Team ID and Bundle Identifier -->
"paths": ["*"] <!-- Paths your app can handle -->
}
]
}
}
In Xcode, enable the Associated Domains entitlement in your app’s target settings. Add the domain to the list of associated domains:
applinks:www.example.com
Step 3: Handling Deep Links in Flutter Code
Once your app is configured to handle deep links, you need to implement the logic to process the incoming links and navigate the user accordingly.
import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'package:flutter/foundation.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? _initialLink;
String? _latestLink;
@override
void initState() {
super.initState();
_initDeepLinkHandling();
}
Future<void> _initDeepLinkHandling() async {
// Get the initial link, if any.
_initialLink = await getInitialLink();
// Handle the initial link.
if (_initialLink != null) {
_handleDeepLink(_initialLink!);
}
// Subscribe to link stream to handle subsequent deep links.
linkStream.listen((String? link) {
setState(() {
_latestLink = link ?? 'Unknown';
if (link != null) {
_handleDeepLink(link);
}
});
}, onError: (err) {
print('Error receiving URI: $err');
});
}
void _handleDeepLink(String link) {
final uri = Uri.parse(link);
if (uri.scheme == 'myapp') {
// Handle custom scheme deep link.
if (uri.host == 'open') {
final param1 = uri.queryParameters['param1'];
// Navigate to a specific screen based on param1.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DeepLinkScreen(param1: param1 ?? 'No parameter'),
),
);
}
} else if (uri.scheme == 'https') {
// Handle universal link.
if (uri.host == 'www.example.com') {
final path = uri.path;
// Navigate to a specific screen based on the path.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UniversalLinkScreen(path: path),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Deep Link Handling'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Initial Link: $_initialLink'),
Text('Latest Link: $_latestLink'),
ElevatedButton(
onPressed: () {
// Manually open a deep link for testing.
_handleDeepLink('myapp://open?param1=test_value');
},
child: const Text('Open Deep Link'),
),
],
),
),
),
);
}
}
class DeepLinkScreen extends StatelessWidget {
final String param1;
const DeepLinkScreen({Key? key, required this.param1}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Deep Link Screen'),
),
body: Center(
child: Text('Parameter Value: $param1'),
),
);
}
}
class UniversalLinkScreen extends StatelessWidget {
final String path;
const UniversalLinkScreen({Key? key, required this.path}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Universal Link Screen'),
),
body: Center(
child: Text('Path: $path'),
),
);
}
}
Explanation:
- Import
uni_linksPackage: Import theuni_linkspackage for deep link handling. Adduni_links: ^0.5.1to yourpubspec.yamland runflutter pub get. - Initialize Deep Link Handling: In
initState, call_initDeepLinkHandling()to initialize deep link processing. - Get Initial Link: Use
getInitialLink()to retrieve the initial deep link that opened the app. - Handle Link Stream: Subscribe to
linkStreamto listen for incoming deep links and handle them in the_handleDeepLink()method. - Parse URI: Use
Uri.parse()to parse the deep link string into aUriobject. - Handle Custom Scheme and Universal Links: Check the
schemeandhostproperties of theUriobject to determine the type of deep link, and navigate accordingly.
Conclusion
Handling different types of deep links is essential for creating a seamless user experience in Flutter apps. By setting up both custom scheme and universal links, and properly handling them in your Flutter code, you can ensure that users are directed to the correct location within your app from various external sources. This leads to increased user engagement and a more satisfying app experience.