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.
StorifyMe provides two approaches for implementing deep links:
Option 1: StorifyMe-Hosted Deep Links (Recommended)
StorifyMe automatically generates and hosts deep linking configuration files for both platforms. This is the simplest approach and requires no backend infrastructure on your end.
Step 1: Configure App Credentials in Dashboard
- Log in to your StorifyMe dashboard
- Navigate to Settings → Mobile
- Configure both platforms:
iOS Configuration:
- Team ID: Your Apple Developer Team ID
- Bundle ID: Your app's bundle identifier (e.g.,
com.yourcompany.app
) - App Store ID: Your app's Apple App Store ID
Android Configuration:
- Package Name: Your app's package name (e.g.,
com.yourcompany.app
) - SHA-256 Fingerprints: Your app signing certificate fingerprints
- Click Save
StorifyMe will automatically generate the configuration files at:
- iOS:
https://storifyme.xyz/.well-known/apple-app-site-association
- Android:
https://storifyme.xyz/.well-known/assetlinks.json
Step 2: Add Dependencies
Add the required packages to your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
storifyme: ^latest_version
uni_links: ^0.5.1 # For handling deep links
# Alternative: Use app_links for newer approach
# app_links: ^3.4.0
Run:
flutter pub get
Step 3: iOS Setup
Configure Associated Domains in Xcode:
- Open your iOS project in Xcode:
ios/Runner.xcworkspace
- Select the Runner target
- Go to Signing & Capabilities
- Click + Capability and add Associated Domains
- Add the following domains:
applinks:storifyme.xyz
applinks:storifyme.com
Step 4: Android Setup
Get SHA-256 Fingerprints:
# Debug keystore
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Release keystore
keytool -list -v -keystore /path/to/your-release-key.keystore -alias your-key-alias
# Play App Signing (from Play Console)
# Go to: Your App → Setup → App integrity → App signing key certificate
Add ALL fingerprints to the StorifyMe dashboard.
Update AndroidManifest.xml:
Open android/app/src/main/AndroidManifest.xml
and add intent filters:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme">
<!-- Your existing intent filters -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- StorifyMe Deep Links - storifyme.xyz -->
<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="storifyme.xyz"
android:pathPrefix="/stories/YOUR_ACCOUNT_ID/" />
</intent-filter>
<!-- StorifyMe Deep Links - storifyme.com -->
<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="storifyme.com"
android:pathPrefix="/stories/YOUR_ACCOUNT_ID/" />
</intent-filter>
<!-- Support for /shorts/ path -->
<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="storifyme.xyz"
android:pathPrefix="/shorts/YOUR_ACCOUNT_ID/" />
</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="storifyme.com"
android:pathPrefix="/shorts/YOUR_ACCOUNT_ID/" />
</intent-filter>
</activity>
Important: Replace YOUR_ACCOUNT_ID
with your actual StorifyMe account ID.
Step 5: Handle Deep Links in Flutter
The deep link URL format is:
https://storifyme.xyz/stories/{ACCOUNT_ID}/{story_handle}
https://storifyme.xyz/shorts/{ACCOUNT_ID}/{story_handle}
Create a deep link handler service:
import 'dart:async';
import 'package:uni_links/uni_links.dart';
import 'package:storifyme_flutter/storifyme_flutter.dart';
class DeepLinkHandler {
StreamSubscription? _sub;
Future<void> init() async {
// Handle initial link (app opened from link)
try {
final initialLink = await getInitialLink();
if (initialLink != null) {
_handleDeepLink(initialLink);
}
} catch (e) {
print('Failed to get initial link: $e');
}
// Handle links when app is already running
_sub = linkStream.listen((String? link) {
if (link != null) {
_handleDeepLink(link);
}
}, onError: (err) {
print('Failed to handle link: $err');
});
}
void _handleDeepLink(String url) {
// Parse the deep link URL
// Format: https://storifyme.xyz/stories/{ACCOUNT_ID}/{story_handle}
try {
final uri = Uri.parse(url);
final pathSegments = uri.pathSegments;
// For StorifyMe-hosted URLs
if (pathSegments.length >= 3 &&
(pathSegments[0] == 'stories' || pathSegments[0] == 'shorts')) {
final storyHandle = pathSegments[2];
// Open the story using StorifyMe SDK
_openStory(storyHandle);
}
} catch (e) {
print('Failed to parse deep link: $e');
}
}
void _openStory(String storyHandle) {
// Open story using StorifyMe SDK
StorifyMeFlutter.openStory(storyHandle);
}
void dispose() {
_sub?.cancel();
}
}
Add the deep link handler to your main app:
import 'package:flutter/material.dart';
import 'package:storifyme_flutter/storifyme_flutter.dart';
import 'services/deep_link_handler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final DeepLinkHandler _deepLinkHandler = DeepLinkHandler();
void initState() {
super.initState();
// Initialize StorifyMe SDK
StorifyMeFlutter.initialize(
accountId: 'YOUR_ACCOUNT_ID',
apiKey: 'YOUR_API_KEY',
);
// Initialize deep link handler
_deepLinkHandler.init();
}
void dispose() {
_deepLinkHandler.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return MaterialApp(
title: 'StorifyMe Demo',
home: HomeScreen(),
);
}
}
Step 6: Testing StorifyMe-Hosted Deep Links
iOS Testing (Simulator):
# Test with storifyme.xyz
xcrun simctl openurl booted "https://storifyme.xyz/stories/YOUR_ACCOUNT_ID/test-story"
# Test with storifyme.com
xcrun simctl openurl booted "https://storifyme.com/stories/YOUR_ACCOUNT_ID/test-story"
Android Testing (ADB):
# Test with storifyme.xyz
adb shell am start \
-W -a android.intent.action.VIEW \
-d "https://storifyme.xyz/stories/YOUR_ACCOUNT_ID/test-story" \
com.yourcompany.app
# Test with storifyme.com
adb shell am start \
-W -a android.intent.action.VIEW \
-d "https://storifyme.com/stories/YOUR_ACCOUNT_ID/test-story" \
com.yourcompany.app
Replace:
YOUR_ACCOUNT_ID
with your StorifyMe account IDcom.yourcompany.app
with your app's package name
Physical Device Testing:
- Send yourself a message with a story link
- Tap the link on your device
- The app should open and display the story
Troubleshooting StorifyMe-Hosted Links
iOS Issues:
- Verify Associated Domains are correctly configured in Xcode
- Clean and rebuild:
flutter clean && flutter pub get && flutter run
- Test on a physical device
- Check AASA file:
curl https://storifyme.xyz/.well-known/apple-app-site-association
Android Issues:
- Verify
android:autoVerify="true"
is set in intent filters - Check path prefix includes your
ACCOUNT_ID
- Verify SHA-256 fingerprints in dashboard
- Clear app defaults: Settings → Apps → Your App → Open by default → Clear defaults
- Check verification:
adb shell pm get-app-links com.yourcompany.app
Option 2: Custom Domain Deep Links (Advanced)
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.
If you want to use your own domain for deep links, you'll need to host the configuration files on your own server.
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 for Custom Domain
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 for Custom Domain
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": ["*"]
}
]
}
}
4. Implementation for Custom Domain
Method 1: Direct Story Launch (Recommended)
For custom domain deep 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;
// For custom domain URLs with various patterns
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 Custom Domain 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