Deep linking is a powerful technique that allows users to navigate directly to specific content within a mobile application from an external source, such as a web page, an email, or another application. In Flutter, handling deep links involves managing various types of links, including URL schemes and App Links (also known as Android App Links and Universal Links on iOS). Properly implementing deep links enhances user experience and engagement by providing a seamless transition into your app.
Understanding Deep Links
Before diving into implementation details, let’s clarify the types of deep links we’ll be addressing:
- URL Schemes: Custom URL schemes (e.g.,
myapp://
) allow you to open your app. While easy to implement, they are not secure and can be claimed by other apps. - App Links (Android) and Universal Links (iOS): These links use standard HTTP/HTTPS URLs that associate your app with a website you own, providing a more secure and reliable deep linking mechanism.
Why Use Deep Links?
- Improved User Experience: Direct users to specific content within the app, streamlining their journey.
- Increased Engagement: Enable easy sharing of app content, driving app usage.
- Marketing and Promotion: Facilitate tracking and attribution for marketing campaigns.
Implementing Deep Links in Flutter
To handle different types of deep links effectively, we’ll use the uni_links
package, which simplifies the process of listening for and parsing incoming links.
Step 1: Add uni_links
Dependency
Add the uni_links
package to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
uni_links: ^0.5.1
Then, run flutter pub get
to install the dependency.
Step 2: Configure URL Schemes
Android Configuration
Open your AndroidManifest.xml
file (usually located in android/app/src/main/
) and add an <intent-filter>
to your <activity>
tag to handle your custom URL scheme:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"> <!-- Add singleTop launchMode -->
<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="myapp" android:host="open"/>
</intent-filter>
</activity>
Here, myapp://open
is the custom URL scheme your app will handle. Also ensure that android:launchMode="singleTop"
is set to properly handle deep links when the app is already running.
iOS Configuration
Open your Info.plist
file (usually located in ios/Runner/
) and add a CFBundleURLTypes
array to define your custom URL scheme:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string> <!-- Replace with your bundle identifier -->
</dict>
</array>
Here, myapp
is the custom URL scheme for your iOS app. Replace com.example.myapp
with your actual bundle identifier.
Step 3: Configure App Links/Universal Links
Android Configuration (App Links)
- Associate Your App with Your Website:
- Create a
assetlinks.json
file and host it at/.well-known/assetlinks.json
on your domain.
The assetlinks.json
file should look like this:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp", <!-- Replace with your package name -->
"sha256_cert_fingerprints": [
"YOUR_SHA256_CERT_FINGERPRINT"
]
}
}
]
- Replace
com.example.myapp
with your app’s package name. - Replace
YOUR_SHA256_CERT_FINGERPRINT
with the SHA256 fingerprint of your app’s signing certificate. You can generate this usingkeytool
.
keytool -list -v -keystore ./path/to/your/keystore.jks -alias your_alias
- Configure
AndroidManifest.xml
: - Add the following
<intent-filter>
to your<activity>
:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"> <!-- Keep singleTop launchMode -->
<intent-filter android:autoVerify="true"> <!-- Add 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"/> <!-- Replace with your domain -->
<data android:scheme="http" android:host="yourdomain.com"/> <!-- Replace with your domain -->
</intent-filter>
<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="myapp" android:host="open"/>
</intent-filter>
</activity>
- Replace
yourdomain.com
with your actual domain. - The
android:autoVerify="true"
attribute enables Android to automatically verify that your app is associated with the domain.
iOS Configuration (Universal Links)
- Associate Your App with Your Website:
- Create an
apple-app-site-association
file and host it at/.well-known/apple-app-site-association
on your domain.
The apple-app-site-association
file should look like this:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "YOUR_TEAM_ID.com.example.myapp", <!-- Replace with your Team ID and Bundle Identifier -->
"paths": [
"/*"
]
}
]
}
}
- Replace
YOUR_TEAM_ID.com.example.myapp
with your Team ID and Bundle Identifier. - The
paths
array specifies which paths on your domain should open your app./*
means all paths.
- Configure Your App in Xcode:
- Enable Associated Domains in your app’s Xcode project:
- Go to your project settings, select your target, and then go to the “Signing & Capabilities” tab.
- Click the “+ Capability” button and add the “Associated Domains” capability.
- Add your domain with the
applinks:
prefix to the Associated Domains list (e.g.,applinks:yourdomain.com
).
Step 4: Handle Incoming Links in Flutter
In your Flutter application, use the uni_links
package to listen for incoming links and navigate accordingly.
import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'package:flutter/services.dart' show PlatformException;
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? _initialUri;
String? _latestUri;
@override
void initState() {
super.initState();
_initUniLinks();
}
Future<void> _initUniLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
final initialUri = await getInitialUri();
// Parse the link and update the state
if (initialUri != null) {
setState(() {
_initialUri = initialUri.toString();
_latestUri = initialUri.toString();
});
_handleDeepLink(initialUri);
}
// Attach a listener to the stream
uriLinkStream.listen((Uri? uri) {
if (uri != null) {
setState(() {
_latestUri = uri.toString();
});
_handleDeepLink(uri);
}
}, onError: (err) {
setState(() {
_latestUri = 'Failed to get latest uri: $err.';
});
});
} on PlatformException {
// Handle exception by warning the user their device isn't supported
print('Failed to get initial uri.');
} on FormatException {
print('Malformed initial uri format.');
}
}
void _handleDeepLink(Uri uri) {
// Implement your logic to navigate to the appropriate screen
if (uri.host == 'open' && uri.scheme == 'myapp') { // For URL Scheme myapp://open
// Example: myapp://open?param1=value1¶m2=value2
String? param1 = uri.queryParameters['param1'];
String? param2 = uri.queryParameters['param2'];
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DeepLinkScreen(uri: uri.toString())),
);
} else if (uri.host == 'yourdomain.com' && (uri.scheme == 'http' || uri.scheme == 'https')) { // For App Links/Universal Links
//Example: https://yourdomain.com/page?id=123
String? pageId = uri.queryParameters['id'];
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DeepLinkScreen(uri: uri.toString())),
);
} else {
print('Unknown deep link: $uri');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('UniLinks Example App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Initial URI: $_initialUrin'),
Text('Latest URI: $_latestUrin'),
],
),
),
),
);
}
}
class DeepLinkScreen extends StatelessWidget {
final String uri;
DeepLinkScreen({Key? key, required this.uri}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Deep Link Content'),
),
body: Center(
child: Text('You were deep linked to: $uri'),
),
);
}
}
void main() {
runApp(MyApp());
}
In this code:
- We initialize
uni_links
in theinitState
method. getInitialUri
fetches the initial URI, if the app was opened from a deep link.uriLinkStream
listens for subsequent deep links.- The
_handleDeepLink
method parses the URI and navigates the user accordingly, providing logic for both URL schemes and App Links/Universal Links.
Testing Deep Links
Testing URL Schemes
On Android, you can use the adb
command:
adb shell am start -a android.intent.action.VIEW -d "myapp://open?param1=value1¶m2=value2" com.example.myapp
On iOS, you can use the xcrun
command (using Simulator):
xcrun simctl openurl booted "myapp://open?param1=value1¶m2=value2"
Testing App Links/Universal Links
For Android, you can use the adb
command:
adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/page?id=123" com.example.myapp
For iOS, simply click the link on a device or simulator to test Universal Links.
Conclusion
Handling deep links effectively in Flutter requires proper configuration for both URL schemes and App Links/Universal Links. Using the uni_links
package simplifies listening for and parsing incoming links, while platform-specific configurations ensure that your app is correctly associated with your desired schemes and domains. By following this guide, you can seamlessly integrate deep linking into your Flutter application, improving user experience and driving engagement.