Deep Linking
Deep linking allows users to open specific StorifyMe stories directly from shared links, providing a seamless experience when content is shared across platforms.
Make sure you have configured your sharing domain in the StorifyMe Dashboard Settings. Set either Custom Domain OR Proxy - this domain will be used when links are copied/shared from your stories.
Flutter Configuration
1. Add Dependencies
Add the required packages to your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
storifyme: ^latest_version
app_links: ^3.4.0 # For handling deep links
2. Android Configuration
Update AndroidManifest.xml
Add an intent filter to your Android manifest:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme">
<!-- Your existing intent filters -->
<!-- Deep link intent filter -->
<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="YOUR_PROXY_DOMAIN" />
</intent-filter>
</activity>
Add assetlinks.json
Create an assetlinks.json
file at https://YOUR_PROXY_DOMAIN/.well-known/assetlinks.json
:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.your.package",
"sha256_cert_fingerprints": [
"YOUR_PACKAGE_FINGERPRINT"
]
}
}
]
3. iOS Configuration
Add Associated Domain
In ios/Runner/Runner.entitlements
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:YOUR_PROXY_DOMAIN</string>
</array>
</dict>
</plist>
Add apple-app-site-association
Create an apple-app-site-association
file at https://YOUR_PROXY_DOMAIN/.well-known/apple-app-site-association
:
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.com.your.package"],
"paths": ["*"]
}
]
}
}
Implementation
Method 1: Direct Story Launch (Recommended)
For shared story links, use direct story opening which doesn't depend on widget loading:
import 'package:flutter/material.dart';
import 'package:storifyme/storifyme.dart';
import 'package:app_links/app_links.dart';
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
final _appLinks = AppLinks();
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initDeepLinks();
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void _initDeepLinks() {
// Handle links when app is already running
_appLinks.uriLinkStream.listen((uri) {
_handleDeepLink(uri);
});
// Handle links when app is launched from link
_appLinks.getInitialAppLink().then((uri) {
if (uri != null) {
_handleDeepLink(uri);
}
});
}
void _handleDeepLink(Uri uri) {
if (uri.scheme == 'https') {
final handle = _extractStoryHandle(uri);
if (handle != null) {
// Launch story directly using StorifyMe
_openStoryByHandle(handle);
}
}
}
String? _extractStoryHandle(Uri uri) {
final pathSegments = uri.pathSegments;
if (pathSegments.isEmpty) return null;
switch (pathSegments.length) {
case 1:
// For URLs like: https://domain.com/story-handle
return pathSegments[0];
case 2:
// For URLs like: https://domain.com/story/story-handle
if (pathSegments[0] == 'story' || pathSegments[0] == 'stories') {
return pathSegments[1];
}
break;
default:
return null;
}
return null;
}
void _openStoryByHandle(String handle) {
// Note: This method depends on your StorifyMe Flutter plugin implementation
// You may need to use the method provided by your plugin
StorifyMe.openStoryByHandle(handle);
}
Widget build(BuildContext context) {
return MaterialApp(
title: 'StorifyMe App',
home: HomePage(),
);
}
}
Method 2: Widget-Based Launch
If you need to open a story within a loaded widget context:
import 'package:flutter/material.dart';
import 'package:storifyme/storifyme.dart';
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late StorifyMeWidget _storifyMeWidget;
String? _pendingHandle;
bool _widgetLoaded = false;
void initState() {
super.initState();
_initStorifyMeWidget();
}
void _initStorifyMeWidget() {
_storifyMeWidget = StorifyMeWidget(
widgetId: 'YOUR_WIDGET_ID',
onLoad: (stories) {
setState(() {
_widgetLoaded = true;
});
// If we have a pending handle, open it now
if (_pendingHandle != null) {
_openStoryInWidget(_pendingHandle!);
_pendingHandle = null;
}
},
onFail: (error) {
// If widget fails to load but we have a handle, try direct launch
if (_pendingHandle != null) {
StorifyMe.openStoryByHandle(_pendingHandle!);
_pendingHandle = null;
}
},
);
}
void handleDeepLink(String handle) {
if (_widgetLoaded) {
// Widget is already loaded, open immediately
_openStoryInWidget(handle);
} else {
// Store handle to open after widget loads
_pendingHandle = handle;
}
}
void _openStoryInWidget(String handle) {
_storifyMeWidget.openStoryByHandle(handle, (result) {
if (!result.success) {
// Story not found in widget, fall back to direct launch
StorifyMe.openStoryByHandle(handle);
}
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('StorifyMe Stories'),
),
body: Column(
children: [
// Your other widgets
Expanded(
child: _storifyMeWidget,
),
],
),
);
}
}
Using GetX Navigation
If you're using GetX for navigation, you can handle deep links in your route management:
import 'package:get/get.dart';
import 'package:storifyme/storifyme.dart';
class AppRoutes {
static String home = '/';
static String story = '/story/:handle';
static List<GetPage> routes = [
GetPage(
name: home,
page: () => HomePage(),
),
GetPage(
name: story,
page: () {
final handle = Get.parameters['handle'];
if (handle != null) {
// Open story directly
StorifyMe.openStoryByHandle(handle);
}
return HomePage(); // Return to home after opening story
},
),
];
}
Using go_router
If you're using go_router for navigation:
import 'package:go_router/go_router.dart';
import 'package:storifyme/storifyme.dart';
final GoRouter router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/story/:handle',
builder: (context, state) {
final handle = state.params['handle'];
if (handle != null) {
// Open story and navigate back to home
WidgetsBinding.instance.addPostFrameCallback((_) {
StorifyMe.openStoryByHandle(handle);
context.go('/');
});
}
return HomePage();
},
),
],
);
Testing Deep Links
Android Testing
Using ADB:
adb shell am start \
-W -a android.intent.action.VIEW \
-d "https://YOUR_PROXY_DOMAIN/your-story-handle" \
com.your.package
iOS Testing
- Use Safari in the iOS Simulator
- Navigate to your deep link URL
- Tap the link to trigger your app
Flutter Testing
You can test deep links in development using:
// Simulate receiving a deep link during development
void _simulateDeepLink() {
final testUri = Uri.parse('https://YOUR_PROXY_DOMAIN/test-story-handle');
_handleDeepLink(testUri);
}
URL Pattern Examples
Your implementation should handle various URL patterns:
URL Pattern | Handle Extraction |
---|---|
https://domain.com/story-handle | pathSegments[0] |
https://domain.com/story/story-handle | pathSegments[1] |
https://domain.com/stories/story-handle | pathSegments[1] |
Best Practices
- Use Direct Launch for Shared Content: Direct story opening is more reliable for shared links
- Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
- Handle App State: Consider whether your app is cold starting or already running
- Error Handling: Handle invalid or expired story handles gracefully
- User Experience: Show loading states while processing deep links
- Navigation: Decide how deep links should affect your app's navigation stack
Performance Considerations
- Lazy Loading: Only initialize deep link handling when needed
- Memory Management: Clean up listeners in dispose methods
- Background Processing: Handle deep links efficiently to avoid blocking the UI
Common Issues
Deep Link Not Working on Android
- Verify
assetlinks.json
is properly formatted and accessible - Check SHA256 fingerprint matches your app signing certificate
- Ensure package name matches exactly
Deep Link Not Working on iOS
- Verify
apple-app-site-association
file is accessible - Check Team ID and bundle identifier are correct
- Use Apple's validation tool to test your association file
Plugin Method Not Found
- Ensure you're using the correct method names from your StorifyMe Flutter plugin
- Check plugin documentation for the exact API
- Verify plugin version compatibility