Deep linking is a powerful technique that allows users to navigate directly to a specific section within an app from external sources like websites, emails, or other apps. In Flutter, implementing robust deep linking can significantly enhance user experience and engagement. This blog post will guide you through handling different types of deep links, including URL schemes and Universal/App Links.
What are Deep Links?
Deep links are URIs that direct users to specific content within a mobile app rather than just opening the app’s home screen. There are primarily two types of deep links:
- URL Schemes: Custom URLs that your app registers to handle (e.g.,
myapp://content). - Universal Links (Android) / App Links (iOS): Standard HTTP URLs that link to both a website and an app. When clicked, the app opens directly without showing an intermediary app chooser.
Why Use Deep Linking?
- Improved User Experience: Direct users to relevant content instantly.
- Marketing Campaigns: Track attribution from specific campaigns.
- Seamless Navigation: Facilitate easy sharing of content within your app.
Setting Up Deep Linking in Flutter
Let’s dive into how to implement deep linking in a Flutter application using both URL schemes and Universal/App Links.
Step 1: Project Setup
Ensure you have a Flutter project set up. If not, create one:
flutter create my_deep_linking_app
Step 2: Adding Dependencies
You’ll need the uni_links package to handle deep links. Add it to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
uni_links: ^0.5.1
Run flutter pub get to install the dependency.
Step 3: Implementing URL Schemes (Custom Schemes)
Android Setup
- Open your
AndroidManifest.xmlfile (android/app/src/main/AndroidManifest.xml). - Add an intent filter inside the
<activity>tag for your main activity:
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<!-- Existing metadata and intent filters -->
<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" />
</intent-filter>
</activity>
Here, myapp is the scheme and open is the host that the app will listen to.
iOS Setup
- Open your
Info.plistfile (ios/Runner/Info.plist). - Add the
CFBundleURLTypesarray with a dictionary for your URL scheme:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
</dict>
</array>
Replace com.example.myapp with your app’s bundle identifier and myapp with your URL scheme.
Flutter Code
In your Flutter app, use the uni_links package to listen for incoming 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<MyApp> {
String? _latestLink = 'Unknown';
@override
void initState() {
super.initState();
_initUniLinks();
}
Future<void> _initUniLinks() async {
// Platform messages may fail, so use a try/catch PlatformException.
try {
final initialLink = await getInitialLink();
// Parse the link and update the state
setState(() {
_latestLink = initialLink ?? 'Unknown';
});
} on PlatformException {
// Handle exception by warning the user their action did not succeed
print('Failed to get initial link.');
}
// Attach a listener to the stream
linkStream.listen((String? link) {
setState(() {
_latestLink = link ?? 'Unknown';
});
}, onError: (err) {
// Handle exception by warning the user their action did not succeed
print('Failed to get initial link.');
});
}
@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 is: $_latestLink'),
),
),
);
}
}
This code initializes the listener for incoming links when the app starts and displays the link in the UI.
Step 4: Implementing Universal Links / App Links
Android Setup
- Create a
assetlinks.jsonfile:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints":
["XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"]
}
}
]
Replace com.example.myapp with your app’s package name and the SHA256 certificate fingerprint of your signing certificate. Generate the SHA256 fingerprint using the following command:
keytool -list -v -keystore my-release-key.keystore -alias alias_name
- Upload the
assetlinks.jsonfile:
Host this file at https://yourdomain.com/.well-known/assetlinks.json. Ensure it’s accessible without redirects and that the content type is application/json.
- Update
AndroidManifest.xml:
<activity android:name=".MainActivity" android:launchMode="singleTop">
<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="yourdomain.com" />
</intent-filter>
</activity>
Add the android:autoVerify="true" attribute and update the <data> tag with your domain. Multiple <data> tags can be used for multiple hosts.
iOS Setup
- Create an
apple-app-site-associationfile:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.myapp",
"paths": ["*"]
}
]
}
}
Replace TEAMID.com.example.myapp with your Team ID and Bundle Identifier.
- Upload the
apple-app-site-associationfile:
Host this file at https://yourdomain.com/.well-known/apple-app-site-association and ensure it’s accessible without redirects and the content type is application/json.
- Configure Associated Domains in Xcode:
- Open your Xcode project.
- Go to your target settings, select “Signing & Capabilities,” and add “Associated Domains.”
- Add
applinks:yourdomain.comto the Associated Domains list.
Flutter Code
The Flutter code to handle the deep link remains the same as with URL schemes:
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<MyApp> {
String? _latestLink = 'Unknown';
@override
void initState() {
super.initState();
_initUniLinks();
}
Future<void> _initUniLinks() async {
// Platform messages may fail, so use a try/catch PlatformException.
try {
final initialLink = await getInitialLink();
// Parse the link and update the state
setState(() {
_latestLink = initialLink ?? 'Unknown';
});
} on PlatformException {
// Handle exception by warning the user their action did not succeed
print('Failed to get initial link.');
}
// Attach a listener to the stream
linkStream.listen((String? link) {
setState(() {
_latestLink = link ?? 'Unknown';
});
}, onError: (err) {
// Handle exception by warning the user their action did not succeed
print('Failed to get initial link.');
});
}
@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 is: $_latestLink'),
),
),
);
}
}
Handling the Deep Link Data
Once you receive the deep link, you need to parse it to extract the necessary data. Here’s how you can modify the Flutter code to handle deep link data:
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<MyApp> {
String? _latestLink = 'Unknown';
String? _contentId;
@override
void initState() {
super.initState();
_initUniLinks();
}
Future<void> _initUniLinks() async {
try {
final initialLink = await getInitialLink();
_parseDeepLink(initialLink);
} on PlatformException {
print('Failed to get initial link.');
}
linkStream.listen((String? link) {
_parseDeepLink(link);
}, onError: (err) {
print('Failed to get initial link.');
});
}
void _parseDeepLink(String? link) {
if (link != null) {
Uri uri = Uri.parse(link);
// Example: Assuming the content ID is in the path segments
if (uri.pathSegments.isNotEmpty) {
setState(() {
_latestLink = link;
_contentId = uri.pathSegments.first;
});
}
} else {
setState(() {
_latestLink = 'Unknown';
_contentId = null;
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Deep Linking Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('The latest deep link is: $_latestLink'),
if (_contentId != null)
Text('Content ID: $_contentId')
else
Text('No content ID found'),
],
),
),
),
);
}
}
In this example, the _parseDeepLink function parses the URI and extracts the content ID from the path segments.
Testing Deep Links
- URL Schemes:
- On Android, use the command:
adb shell am start -W -a android.intent.action.VIEW -d "myapp://open?content=123" com.example.myapp - On iOS, open Safari and type
myapp://open?content=123.
- On Android, use the command:
- Universal/App Links:
- Ensure the
assetlinks.jsonorapple-app-site-associationfile is correctly set up on your domain. - Click on a link (e.g.,
https://yourdomain.com/content/123) on a device or simulator/emulator.
- Ensure the
Conclusion
Deep linking in Flutter enhances user engagement and provides a seamless navigation experience. By implementing both URL schemes and Universal/App Links, you ensure wide compatibility and a robust linking strategy. Proper setup of AndroidManifest.xml, Info.plist, assetlinks.json, and apple-app-site-association files, combined with the uni_links package, provides a comprehensive solution for handling different types of deep links in your Flutter app. Always remember to thoroughly test your deep links on both Android and iOS devices to ensure they work as expected.
