Deep links are a powerful mechanism for directing users to specific sections within a mobile application. In Flutter, effectively handling custom deep links requires a strategic approach to ensure a seamless user experience. This blog post will guide you through implementing custom deep link handling logic in a Flutter application, covering setup, best practices, and advanced techniques.
What are Deep Links?
Deep links are Uniform Resource Identifiers (URIs) that direct users to specific content within a mobile app rather than just opening the app’s home page. They enhance user experience by directly taking users to relevant content, whether it’s a specific product, a promotion, or a particular feature.
Why Implement Custom Deep Link Handling?
- Improved User Experience: Direct users straight to relevant content, making navigation smoother and more intuitive.
- Enhanced Marketing Campaigns: Track and optimize user engagement through custom deep links in promotional materials.
- Seamless Integration: Facilitate seamless integration with web content, other apps, and promotional campaigns.
Setting Up Deep Link Handling in Flutter
To implement deep link handling, you’ll need to configure both the Android and iOS platforms and then use Flutter code to handle incoming links.
Step 1: Android Configuration
Update your AndroidManifest.xml file (located at android/app/src/main/AndroidManifest.xml) to register an intent filter for your desired URI scheme.
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<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="yourcustomscheme" android:host="yourapp.com" />
</intent-filter>
</activity>
- Ensure
android:launchMode="singleTask"to handle the deep link properly when the app is already running. - Replace
yourcustomschemewith your desired scheme (e.g.,myapp,customapp). - Set the
android:hostto your domain (e.g.,yourapp.com).
Step 2: iOS Configuration
For iOS, you’ll need to configure the Info.plist file (located at ios/Runner/Info.plist).
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourcustomscheme</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.yourapp</string>
</dict>
</array>
- Replace
yourcustomschemewith your custom scheme. - Set
CFBundleURLNameto your app’s bundle identifier.
Additionally, for Universal Links (using https scheme), you need to configure Associated Domains in your Xcode project and upload an apple-app-site-association file to your website. That file should be placed at `/.well-known/apple-app-site-association` and `/apple-app-site-association` to support older versions of iOS.
Step 3: Flutter Code for Handling Deep Links
Use the uni_links package to listen for incoming deep links in your Flutter application.
dependencies:
flutter:
sdk: flutter
uni_links: ^0.5.1
Then, implement the following Flutter code to listen for deep links:
import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
String? _latestLink = 'Unknown';
StreamSubscription? _sub;
@override
void initState() {
super.initState();
_initDeepLinkHandling();
}
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
Future<void> _initDeepLinkHandling() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
final initialLink = await getInitialLink();
if (initialLink != null) {
print('Initial Link: $initialLink');
_updateLink(initialLink);
}
} on PlatformException {
print('Failed to get initial link.');
}
_sub = linkStream.listen((String? link) {
if (link != null) {
print('Incoming Link: $link');
_updateLink(link);
}
}, onError: (err) {
print('Error: $err');
});
}
void _updateLink(String link) {
setState(() {
_latestLink = link;
_handleDeepLink(link);
});
}
void _handleDeepLink(String link) {
// Parse the link and navigate to the relevant section of the app.
final uri = Uri.parse(link);
if (uri.scheme == 'yourcustomscheme' && uri.host == 'yourapp.com') {
final path = uri.pathSegments;
if (path.isNotEmpty) {
switch (path[0]) {
case 'product':
// Navigate to product details page.
print('Navigating to product: ${path[1]}');
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProductDetailsPage(productId: path[1]),
));
break;
case 'profile':
// Navigate to user profile page.
print('Navigating to profile: ${path[1]}');
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProfilePage(userId: path[1]),
));
break;
default:
print('Unknown path: ${path[0]}');
}
}
} else {
print('Invalid deep link: $link');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Deep Link Example'),
),
body: Center(
child: Text('Latest Link: $_latestLinkn'),
),
),
);
}
}
class ProductDetailsPage extends StatelessWidget {
final String productId;
ProductDetailsPage({required this.productId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Product Details'),
),
body: Center(
child: Text('Product ID: $productId'),
),
);
}
}
class ProfilePage extends StatelessWidget {
final String userId;
ProfilePage({required this.userId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile Page'),
),
body: Center(
child: Text('User ID: $userId'),
),
);
}
}
- The
_initDeepLinkHandling()method is responsible for initializing deep link listening. It gets the initial link (if the app was opened via a deep link) and then listens for subsequent deep links. - The
linkStreamprovides a stream of incoming deep links, allowing you to react in real-time. - The
_handleDeepLink()method parses the link and navigates to the relevant section of the app.
Advanced Techniques for Deep Link Handling
1. Using Universal Links
Universal Links are standard HTTP or HTTPS links that redirect users to specific content within your app, improving security and SEO benefits over custom schemes. Implementing Universal Links requires uploading an `apple-app-site-association` file to your domain and configuring associated domains in your iOS project.
2. Deferred Deep Linking
Deferred deep linking allows you to track and attribute installs resulting from deep links, especially when a user installs the app after clicking a deep link but before opening it. Use services like Branch or Adjust to handle deferred deep linking, providing a seamless experience even if the app wasn’t initially installed.
3. Error Handling and Validation
Always include error handling to manage unexpected deep link formats or incorrect parameters. Validate parameters to ensure they conform to the expected data types and constraints.
Best Practices for Implementing Custom Deep Link Handling
- Use Consistent URI Schemes: Choose a consistent and descriptive URI scheme.
- Handle Edge Cases: Handle cases where the app isn’t installed or when parameters are missing or invalid.
- Test Thoroughly: Test deep links on both Android and iOS devices to ensure they function as expected.
- Document Your Deep Links: Maintain clear documentation of your deep link structure and parameters.
- Ensure Security: Sanitize and validate parameters passed through deep links to prevent security vulnerabilities.
Conclusion
Implementing custom deep link handling in Flutter involves configuring native platforms, listening for deep links using the uni_links package, and navigating users to specific sections of your app. By following the steps and best practices outlined in this guide, you can create a seamless and engaging user experience through effective deep linking.