Deep linking is a powerful technique that allows users to navigate directly to a specific section of an application by clicking a link. In Flutter, implementing deep linking can significantly enhance user experience and engagement. This comprehensive guide will walk you through the process of implementing deep linking in your Flutter applications.
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 application rather than simply launching the app. It allows external sources such as web pages, emails, social media posts, or even other applications to direct users to specific content within an app.
Why Use Deep Linking in Flutter?
- Enhanced User Experience: Provides a direct route to relevant content within the app.
- Improved User Engagement: Simplifies the process of accessing specific features or content.
- Seamless Navigation: Facilitates navigation from various external platforms to specific sections within your app.
- Marketing and Analytics: Supports campaign tracking by routing users through trackable deep links.
Types of Deep Links
- URI Schemes: Use a custom URL scheme (e.g.,
myapp://) to open the app. - Universal Links (iOS) and App Links (Android): Use standard HTTP/HTTPS links that are associated with your app via domain verification.
Implementing Deep Linking in Flutter
To implement deep linking in Flutter, you can use various packages and approaches, depending on the complexity and requirements of your application. The most common methods involve using the uni_links or app_links packages.
Method 1: Using the uni_links Package
The uni_links package provides a simple way to handle both URI scheme and universal/app links.
Step 1: Add the uni_links Dependency
Include the uni_links package in your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
uni_links: ^0.5.1
Run flutter pub get to install the package.
Step 2: Configure the Native Platforms
Android Configuration:
In your AndroidManifest.xml file (located at android/app/src/main/AndroidManifest.xml), add an intent-filter to your main activity:
<!-- Specify that this activity can be launched via deep links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accept URIs that begin with "myapp://"-->
<data android:scheme="myapp" />
</intent-filter>
<!-- Add other intent filters as needed -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
iOS Configuration:
In your Info.plist file (located at ios/Runner/Info.plist), add a CFBundleURLTypes array:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string> <!-- Replace with your app's bundle identifier -->
</dict>
</array>
Make sure to replace myapp with your custom URL scheme and com.example.myapp with your app’s bundle identifier.
Step 3: Implement the Deep Link Handling Logic in Flutter
In your Flutter app, add the code to listen for incoming deep links:
import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'package:flutter/services.dart' show PlatformException;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
String? _latestLink = 'Unknown';
@override
void initState() {
super.initState();
_initURIHandler();
_incomingLinkHandler();
}
/// Handle initial URI link
Future _initURIHandler() async {
if (!mounted) return;
try {
final initialLink = await getInitialUri();
if (initialLink != null) {
debugPrint('Initial URI received $initialLink');
_updateLink(initialLink.toString());
} else {
debugPrint('Null initial URI received');
}
} on PlatformException {
debugPrint('Failed to get initial URI');
} on FormatException catch (err) {
if (!mounted) return;
debugPrint('Malformed Initial URI received: $err');
}
}
/// Handle incoming links - the ones that the application receives when it's already started
void _incomingLinkHandler() {
if (!mounted) return;
uriLinkStream.listen((Uri? uri) {
if (!mounted) return;
print('Received URI: $uri');
_updateLink(uri?.toString() ?? 'Unknown');
}, onError: (Object err) {
if (!mounted) return;
print('Error occurred: $err');
_updateLink('Unknown');
});
}
void _updateLink(String newLink) {
setState(() {
_latestLink = newLink;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Deep Linking Example'),
),
body: Center(
child: Text('The latest deep link URI is: $_latestLink.'),
),
),
);
}
}
In this example, _initURIHandler() checks for an initial deep link when the app starts, and _incomingLinkHandler() listens for incoming deep links while the app is running. The _updateLink() method updates the UI with the received link.
Step 4: Handle the Deep Link Logic
Based on the deep link received, you can navigate the user to specific parts of the app. For example:
void _handleDeepLink(String link) {
if (link.startsWith('myapp://product/')) {
String productId = link.substring('myapp://product/'.length);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsPage(productId: productId),
),
);
}
}
Update _updateLink method to call _handleDeepLink:
void _updateLink(String newLink) {
setState(() {
_latestLink = newLink;
});
_handleDeepLink(newLink);
}
Method 2: Using the app_links Package for Universal/App Links
For handling universal links on iOS and app links on Android, the app_links package is useful.
Step 1: Add the app_links Dependency
Include the app_links package in your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
app_links: ^3.4.0
Run flutter pub get to install the package.
Step 2: Configure the Native Platforms for Universal Links/App Links
Android Configuration (App Links):
In your AndroidManifest.xml file, add the intent-filter for your website:
<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="www.example.com" android:pathPrefix="/product" />
</intent-filter>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
Make sure to set android:autoVerify="true" to enable auto verification of the app link.
You also need to serve a Digital Asset Links JSON file (assetlinks.json) at https://www.example.com/.well-known/assetlinks.json. The file should look like this:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": [
"YOUR_SHA256_CERT_FINGERPRINT"
]
}
}
]
Replace com.example.myapp with your app’s package name and YOUR_SHA256_CERT_FINGERPRINT with the SHA256 fingerprint of your app’s signing certificate.
iOS Configuration (Universal Links):
You need to associate your app with your website by creating an apple-app-site-association file and placing it at https://www.example.com/.well-known/apple-app-site-association. The file should look like this:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "YOUR_TEAM_ID.com.example.myapp",
"paths": [ "/product/*" ]
}
]
}
}
Replace YOUR_TEAM_ID.com.example.myapp with your app’s Team ID and Bundle Identifier. You also need to enable Associated Domains in your app’s entitlements and add applinks:www.example.com to the Associated Domains list.
Step 3: Implement the Deep Link Handling Logic in Flutter
In your Flutter app, add the code to listen for incoming deep links:
import 'package:flutter/material.dart';
import 'package:app_links/app_links.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
String? _latestLink = 'Unknown';
final _appLinks = AppLinks();
@override
void initState() {
super.initState();
initDeepLinks();
}
Future initDeepLinks() async {
// Check initial link if app was launched from a link
final appLink = await _appLinks.getInitialLink();
if (appLink != null) {
print('Initial app link: $appLink');
_updateLink(appLink);
}
// Handle link when app is in the background and comes to the foreground
_appLinks.uriLinkStream.listen((uri) {
print('Received URI: $uri');
_updateLink(uri.toString());
}, onError: (err) {
print('Error occurred: $err');
});
}
void _updateLink(String newLink) {
setState(() {
_latestLink = newLink;
});
_handleDeepLink(newLink);
}
void _handleDeepLink(String link) {
if (link.startsWith('https://www.example.com/product/')) {
String productId = link.substring('https://www.example.com/product/'.length);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailsPage(productId: productId),
),
);
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Deep Linking Example'),
),
body: Center(
child: Text('The latest deep link URI is: $_latestLink.'),
),
),
);
}
}
Testing Deep Linking
Android Testing:
You can test deep links on Android using the adb command:
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123" com.example.myapp
iOS Testing:
You can test universal links on iOS by sending a test email or message containing the link and opening it on your device.
Best Practices for Deep Linking
- Handle Errors Gracefully: Implement error handling for malformed or invalid deep links.
- Provide Fallbacks: Redirect users to a default page if the deep link is not correctly formatted.
- Use Consistent Naming Conventions: Maintain consistency in your deep link structures.
- Test Thoroughly: Test deep links on various devices and platforms to ensure they function correctly.
- Secure Your Deep Links: Protect your deep links to prevent malicious use.
Conclusion
Implementing deep linking in Flutter applications can greatly enhance user experience and engagement. By using packages like uni_links and app_links, you can effectively handle both URI scheme and universal/app links. Following best practices will ensure that your deep linking implementation is robust and user-friendly.