Deep linking is a crucial feature in modern mobile applications. It allows you to direct users to a specific section or page within your app from an external source, such as a website, another app, or a marketing campaign. In Flutter, handling deep links effectively enhances user experience and improves app engagement. One of the popular packages for managing deep links in Flutter is uni_links. This blog post explores how to use the uni_links package to handle deep links in your Flutter application.
What are Deep Links?
Deep links are URIs (Uniform Resource Identifiers) that direct users to a specific location within a mobile app instead of just launching the app’s home page. They provide a seamless experience for users coming from various external sources.
Why Use Deep Links?
- Improved User Experience: Direct users to the relevant content within your app immediately.
- Enhanced Engagement: Promote specific features or content in marketing campaigns.
- Seamless Integration: Integrate your app with other apps and websites for a smooth user journey.
Introduction to the uni_links Package
The uni_links package in Flutter provides a simple and efficient way to handle incoming deep links and app links. It supports both Android and iOS platforms, making it a versatile choice for cross-platform Flutter development.
How to Implement Deep Linking Using uni_links
Follow these steps to implement deep linking in your Flutter application using the uni_links package.
Step 1: Add the uni_links Dependency
Add the uni_links package to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
uni_links: ^0.5.1 # Use the latest version
Run flutter pub get to install the package.
Step 2: Configure Your App for Deep Linking
Android Configuration
In your AndroidManifest.xml file (android/app/src/main/AndroidManifest.xml), add an intent filter to the relevant activity. This filter specifies which URI schemes and hosts your app can handle.
<!-- Add this attribute -->
<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="yourapp" android:host="yourdomain.com" />
</intent-filter>
</activity>
Replace yourapp and yourdomain.com with your actual app’s URI scheme and host.
iOS Configuration
In your Info.plist file (ios/Runner/Info.plist), add a CFBundleURLTypes array to define the URL schemes your app can handle.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.yourdomain.yourapp</string>
</dict>
</array>
Replace yourapp and com.yourdomain.yourapp with your actual app’s URL scheme and bundle identifier.
Step 3: Implement Deep Link Handling in Flutter
In your main Flutter widget, listen for incoming links using uni_links and handle them accordingly.
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';
String? _latestUri;
@override
void initState() {
super.initState();
_initDeepLinkHandling();
}
Future _initDeepLinkHandling() async {
// Attach a listener to the stream of incoming links
_handleIncomingLinks();
// Check if app was opened with a link
_handleInitialUri();
}
Future _handleIncomingLinks() async {
uriLinkStream.listen((Uri? uri) {
if (!mounted) return;
print('Received URI: $uri');
setState(() {
_latestUri = uri?.toString() ?? 'Unknown';
_latestLink = uri?.path ?? 'Unknown'; // Extracting the path from the URI
});
// Handle the deep link based on its path
_handleDeepLink(_latestLink);
}, onError: (Object err) {
if (!mounted) return;
print('Error occurred: $err');
setState(() {
_latestUri = 'Failed to get latest link: $err.';
_latestLink = 'Failed to get latest link: $err.';
});
});
}
Future _handleInitialUri() async {
// Platform messages may fail, so use a try/catch PlatformException.
try {
final uri = await getInitialUri();
if (uri == null) {
print('No initial URI received');
} else {
print('Initial URI received: $uri');
}
if (!mounted) return;
setState(() {
_latestUri = uri?.toString() ?? 'Unknown';
_latestLink = uri?.path ?? 'Unknown'; // Extracting the path from the URI
});
// Handle the deep link based on its path
_handleDeepLink(_latestLink);
} on PlatformException {
// Platform messages may fail, so we use a try/catch PlatformException.
print('Failed to get initial URI.');
if (!mounted) return;
setState(() {
_latestUri = 'Failed to get initial URI.';
_latestLink = 'Failed to get initial URI.';
});
} on FormatException catch (err) {
if (!mounted) return;
print('Malformed Initial URI received: $err');
setState(() {
_latestUri = 'Malformed Initial URI received: $err';
_latestLink = 'Malformed Initial URI received: $err';
});
}
}
void _handleDeepLink(String? path) {
if (path != null) {
switch (path) {
case '/profile':
// Navigate to profile page
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ProfilePage()));
break;
case '/settings':
// Navigate to settings page
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SettingsPage()));
break;
default:
// Handle unknown deep link
print('Unknown deep link: $path');
break;
}
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('UniLinks Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Latest link received: $_latestLink'),
Text('Latest URI received: $_latestUri'),
],
),
),
),
);
}
}
class ProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Profile')),
body: Center(child: Text('Profile Page')),
);
}
}
class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Settings')),
body: Center(child: Text('Settings Page')),
);
}
}
In this code:
- We initialize the
uni_linkslistener in theinitStatemethod. _handleIncomingLinks()listens for incoming deep links when the app is already running._handleInitialUri()checks for a deep link when the app is launched from a terminated state.- The path is extracted from the URI and used in
_handleDeepLink(path)for navigation
Step 4: Testing Deep Links
To test deep links, you can use the following commands:
Android
adb shell am start -W -a android.intent.action.VIEW -d "yourapp://yourdomain.com/profile" your.package.name
iOS
xcrun simctl openurl booted "yourapp://yourdomain.com/profile"
Replace yourapp, yourdomain.com, /profile, and your.package.name with your actual values.
Best Practices for Using Deep Links
- Handle All Cases: Ensure your app gracefully handles various deep link scenarios, including invalid or unexpected links.
- Error Handling: Implement robust error handling to catch any exceptions during deep link processing.
- User Redirection: Always redirect users to the appropriate content within the app, even if the deep link is temporarily unavailable.
Conclusion
Deep linking is a powerful tool for enhancing user engagement and providing a seamless app experience. By using the uni_links package in Flutter, you can easily implement and manage deep links across both Android and iOS platforms. Following the steps and best practices outlined in this blog post will help you create a robust and user-friendly deep linking solution for your Flutter application.