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.xyzapplinks: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_IDwith your StorifyMe account IDcom.yourcompany.appwith 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.
For a custom domain connected directly to StorifyMe, StorifyMe generates the association files automatically. Follow the custom-domain app links guide for the required dashboard settings and app configuration. The manual file-hosting instructions below apply to customer-managed proxies.
If your domain is served through a customer-managed proxy, configure the proxy to forward the association-file requests or host those files on the proxy. The app-side steps below apply to both direct custom domains and proxies.
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>
Host or forward assetlinks.json (proxy domains only):
For a customer-managed proxy, host assetlinks.json at https://YOUR_PROXY_DOMAIN/.well-known/assetlinks.json. Proxy forwarding to StorifyMe requires a separate, validated proxy-specific configuration:
[
{
"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>
Host or forward apple-app-site-association (proxy domains only):
For a customer-managed proxy, host the AASA file at https://YOUR_PROXY_DOMAIN/.well-known/apple-app-site-association. Proxy forwarding to StorifyMe requires a separate, validated proxy-specific configuration:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "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.jsonis 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-associationfile 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