Deep linking in Flutter allows users to navigate directly to specific sections or pages within an app from external sources, such as web pages, emails, or other applications. This powerful feature enhances the user experience by providing seamless navigation and direct access to relevant content. Implementing deep linking in Flutter can be achieved through various methods, each with its own advantages and considerations.
What is Deep Linking?
Deep linking is a technique that uses a uniform resource identifier (URI) to link to a specific location within a mobile application rather than simply launching the app. This allows the application to respond to the URI by navigating the user directly to the specified content or action. There are primarily two types of deep linking: standard deep linking and deferred deep linking. Standard deep linking works when the app is already installed on the device, while deferred deep linking handles the case where the app needs to be installed first.
Why Use Deep Linking?
- Enhanced User Experience: Provides direct access to specific content, reducing friction.
- Improved Engagement: Drives users from external sources directly to relevant app features.
- Better Conversion Rates: Streamlines the user journey for targeted actions.
How to Implement Deep Linking in Flutter
Implementing deep linking in Flutter involves several steps, including configuring the app manifest for URI schemes and handling the incoming links within the app.
Method 1: Using the url_launcher Package
The url_launcher package is a common and straightforward way to handle deep links in Flutter. It allows your app to open URLs, and by parsing these URLs, you can navigate within your application.
Step 1: Add Dependency
Add the url_launcher package to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.1.5
Then, run flutter pub get to install the package.
Step 2: Configure the App Manifest (Android)
In your AndroidManifest.xml file (located in android/app/src/main/), add an intent-filter to the <activity> tag. This filter will capture specific URI schemes that your app can handle.
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<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"/> <!-- Replace with your scheme -->
<data android:host="open"/> <!-- Replace with your host -->
</intent-filter>
</activity>
Replace "myapp" with your desired URI scheme and "open" with your host.
Step 3: Configure the App (iOS)
For iOS, you need to configure the Info.plist file (located in ios/Runner/) to specify the URL schemes that your app can handle.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string> <!-- Replace with your scheme -->
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string> <!-- Replace with your app's bundle identifier -->
</dict>
</array>
Replace "myapp" with your desired URI scheme and "com.example.myapp" with your app’s bundle identifier.
Step 4: Handle Incoming Links in Flutter
In your Flutter app, use the url_launcher package to listen for incoming links and navigate accordingly. Here’s an example using a StatefulWidget:
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:async';
class DeepLinkingHandler extends StatefulWidget {
@override
_DeepLinkingHandlerState createState() => _DeepLinkingHandlerState();
}
class _DeepLinkingHandlerState extends State<DeepLinkingHandler> {
String? _incomingLink;
StreamSubscription? _sub;
@override
void initState() {
super.initState();
_initDeepLinkHandling();
}
Future<void> _initDeepLinkHandling() async {
// Check if app was started with a link
final initialLink = await getInitialUri();
if (initialLink != null) {
setState(() {
_incomingLink = initialLink.toString();
});
_handleDeepLink(initialLink);
}
// Subscribe to receive updates when app is already running
_sub = uriLinkStream.listen((Uri? uri) {
if (uri != null) {
setState(() {
_incomingLink = uri.toString();
});
_handleDeepLink(uri);
}
}, onError: (err) {
print('Error receiving URI: $err');
});
}
void _handleDeepLink(Uri link) {
// Example: myapp://open?route=/details&id=123
if (link.host == 'open') {
final route = link.queryParameters['route'];
final id = link.queryParameters['id'];
if (route == '/details' && id != null) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => DetailsPage(id: id),
));
}
}
}
@override
void dispose() {
super.dispose();
_sub?.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Deep Linking Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Incoming Link:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(_incomingLink ?? 'No link received'),
],
),
),
);
}
}
class DetailsPage extends StatelessWidget {
final String id;
DetailsPage({required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details Page'),
),
body: Center(
child: Text('Details ID: $id'),
),
);
}
}
This code does the following:
- It initializes the
url_launcherplugin and listens for incoming links usinggetInitialUri()anduriLinkStream. - It checks if the app was started with a deep link by calling
getInitialUri()in theinitState()method. - It subscribes to the
uriLinkStreamstream, which emits incoming URIs while the app is running. - The
_handleDeepLink()method parses the link and navigates to the appropriate screen usingNavigator.push().
Method 2: Using the uni_links Package
The uni_links package provides a more robust solution for handling deep links, including deferred deep linking (handling links after the app is installed).
Step 1: Add 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 package.
Step 2: Configure the App Manifest (Android)
As with the url_launcher method, add an intent-filter to your AndroidManifest.xml file:
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<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"/> <!-- Replace with your scheme -->
<data android:host="open"/> <!-- Replace with your host -->
</intent-filter>
</activity>
Replace "myapp" with your desired URI scheme and "open" with your host.
Step 3: Configure the App (iOS)
For iOS, configure the Info.plist file as well:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string> <!-- Replace with your scheme -->
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string> <!-- Replace with your app's bundle identifier -->
</dict>
</array>
Replace "myapp" with your desired URI scheme and "com.example.myapp" with your app’s bundle identifier.
Step 4: Handle Incoming Links in Flutter
Here’s an example of using uni_links to listen for incoming links:
import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'dart:async';
import 'package:flutter/services.dart';
class DeepLinkingHandler extends StatefulWidget {
@override
_DeepLinkingHandlerState createState() => _DeepLinkingHandlerState();
}
class _DeepLinkingHandlerState extends State<DeepLinkingHandler> {
String? _incomingLink;
StreamSubscription? _sub;
@override
void initState() {
super.initState();
_initDeepLinkHandling();
}
Future<void> _initDeepLinkHandling() async {
try {
// Check if app was started with a link
final initialLink = await getInitialLink();
if (initialLink != null) {
setState(() {
_incomingLink = initialLink;
});
_handleDeepLink(initialLink);
}
// Subscribe to receive updates when app is already running
_sub = linkStream.listen((String? link) {
if (link != null) {
setState(() {
_incomingLink = link;
});
_handleDeepLink(link);
}
}, onError: (err) {
print('Error receiving URI: $err');
});
} on PlatformException {
print('Failed to get initial link.');
} on FormatException {
print('Failed to parse initial link as Uri.');
}
}
void _handleDeepLink(String link) {
final uri = Uri.parse(link);
// Example: myapp://open?route=/details&id=123
if (uri.host == 'open') {
final route = uri.queryParameters['route'];
final id = uri.queryParameters['id'];
if (route == '/details' && id != null) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => DetailsPage(id: id),
));
}
}
}
@override
void dispose() {
super.dispose();
_sub?.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Deep Linking Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Incoming Link:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(_incomingLink ?? 'No link received'),
],
),
),
);
}
}
class DetailsPage extends StatelessWidget {
final String id;
DetailsPage({required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details Page'),
),
body: Center(
child: Text('Details ID: $id'),
),
);
}
}
This code does the following:
- It initializes the
uni_linksplugin and listens for incoming links usinggetInitialLink()andlinkStream. - It checks if the app was started with a deep link by calling
getInitialLink()in theinitState()method. - It subscribes to the
linkStreamstream, which emits incoming links as strings. - The
_handleDeepLink()method parses the link and navigates to the appropriate screen usingNavigator.push().
Conclusion
Implementing deep linking in Flutter can significantly enhance the user experience and engagement with your app. Whether using the url_launcher package for simpler implementations or the uni_links package for more robust solutions, the process involves configuring the app manifest, listening for incoming links, and navigating accordingly. Deep linking provides a seamless way to connect external sources to specific content within your Flutter application.