Skip to main content

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.

Before you begin

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:

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:

android/app/src/main/AndroidManifest.xml
<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:

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:

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:

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:

main.dart
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:

home_page.dart
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:

app_routes.dart
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:

router.dart
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();
},
),
],
);

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

  1. Use Safari in the iOS Simulator
  2. Navigate to your deep link URL
  3. 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 PatternHandle Extraction
https://domain.com/story-handlepathSegments[0]
https://domain.com/story/story-handlepathSegments[1]
https://domain.com/stories/story-handlepathSegments[1]

Best Practices

  1. Use Direct Launch for Shared Content: Direct story opening is more reliable for shared links
  2. Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
  3. Handle App State: Consider whether your app is cold starting or already running
  4. Error Handling: Handle invalid or expired story handles gracefully
  5. User Experience: Show loading states while processing deep links
  6. Navigation: Decide how deep links should affect your app's navigation stack

Performance Considerations

  1. Lazy Loading: Only initialize deep link handling when needed
  2. Memory Management: Clean up listeners in dispose methods
  3. Background Processing: Handle deep links efficiently to avoid blocking the UI

Common Issues

  • Verify assetlinks.json is properly formatted and accessible
  • Check SHA256 fingerprint matches your app signing certificate
  • Ensure package name matches exactly
  • 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