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.
Android Configuration
1. Update AndroidManifest.xml
Add an intent filter to your main activity (or dedicated deep link activity) to handle incoming links:
<activity android:name=".MainActivity">
<!-- 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"
android:pathPrefix="/" />
</intent-filter>
</activity>
Replace YOUR_PROXY_DOMAIN
with the domain you configured in the StorifyMe Dashboard.
2. Backend Configuration
Create an assetlinks.json
file in your backend 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"
]
}
}
]
You can get your app's SHA256 fingerprint using:
keytool -list -v -keystore your-release-key.keystore -alias your-key-alias
Implementation
Method 1: Direct Story Launch (Recommended)
For shared story links, use PreviewStoryByHandleLauncher
to open stories directly without depending on widget loading:
import com.storify.android_sdk.PreviewStoryByHandleLauncher
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Handle deep link on app launch
handleDeepLink(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Handle deep link when app is already running
intent?.let { handleDeepLink(it) }
}
private fun handleDeepLink(intent: Intent) {
val uri = intent.data
if (uri != null && uri.scheme == "https") {
val handle = extractStoryHandle(uri)
if (handle != null) {
// Launch story directly
PreviewStoryByHandleLauncher.INSTANCE.launch(this, handle)
}
}
}
private fun extractStoryHandle(uri: Uri): String? {
return when {
// For URLs like: https://domain.com/story-handle
uri.pathSegments.size == 1 -> uri.pathSegments[0]
// For URLs like: https://domain.com/story/story-handle
uri.pathSegments.size >= 2 && uri.pathSegments[0] == "story" ->
uri.pathSegments[1]
// For URLs like: https://domain.com/stories/story-handle
uri.pathSegments.size >= 2 && uri.pathSegments[0] == "stories" ->
uri.pathSegments[1]
else -> null
}
}
}
Method 2: Widget-Based Launch
If you need to open a story within a loaded widget context:
class MainActivity : AppCompatActivity() {
private lateinit var storiesView: StoriesView
private var pendingHandle: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
storiesView = findViewById(R.id.storiesView)
setupStoriesView()
// Handle deep link
handleDeepLink(intent)
}
private fun setupStoriesView() {
StorifyMe.instance.eventListener = object : StorifyMeEventListener() {
override fun onLoad(widgetId: Long, stories: List<StoryWithSeen>) {
super.onLoad(widgetId, stories)
// If we have a pending handle, open it now
pendingHandle?.let { handle ->
storiesView.openWidgetStoryByHandle(handle) { result ->
when (result) {
is StorifyMeWidgetStoryNavigatorExecutionResult.Success -> {
// Story opened successfully
}
is StorifyMeWidgetStoryNavigatorExecutionResult.StoryNotFound -> {
// Handle not found in this widget
// Consider using PreviewStoryByHandleLauncher as fallback
PreviewStoryByHandleLauncher.INSTANCE.launch(this@MainActivity, handle)
}
is StorifyMeWidgetStoryNavigatorExecutionResult.WidgetNotLoaded -> {
// Widget not ready yet
}
}
}
pendingHandle = null
}
}
override fun onFail(widgetId: Long, error: String) {
super.onFail(widgetId, error)
// If widget fails to load but we have a handle, try direct launch
pendingHandle?.let { handle ->
PreviewStoryByHandleLauncher.INSTANCE.launch(this@MainActivity, handle)
pendingHandle = null
}
}
}
storiesView.widgetId = YOUR_WIDGET_ID
storiesView.load()
}
private fun handleDeepLink(intent: Intent) {
val uri = intent.data
if (uri != null) {
val handle = extractStoryHandle(uri)
if (handle != null) {
if (storiesView.isLoaded()) {
// Widget is already loaded, open immediately
storiesView.openWidgetStoryByHandle(handle)
} else {
// Store handle to open after widget loads
pendingHandle = handle
}
}
}
}
}
URL Pattern Examples
Your implementation should handle various URL patterns depending on your backend setup:
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] |
https://domain.com/content/story-handle | pathSegments[1] |
Testing Deep Links
Using ADB
Test your deep link implementation using ADB:
adb shell am start \
-W -a android.intent.action.VIEW \
-d "https://YOUR_PROXY_DOMAIN/your-story-handle" \
com.your.package
Using Intent Filter Testing
You can also test using Android's intent filter verification:
adb shell am start \
-a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://YOUR_PROXY_DOMAIN/your-story-handle"
Best Practices
- Use Direct Launch for Shared Content:
PreviewStoryByHandleLauncher
is more reliable for shared links as it doesn't depend on widget loading - Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
- Handle Multiple URL Patterns: Make your URL parsing robust to handle different backend configurations
- Validate Handles: Check if handles exist before attempting to open stories
- User Experience: Show loading states while processing deep links
- Error Handling: Gracefully handle invalid or expired story handles
Common Issues
Deep Link Not Triggering
- Verify your
assetlinks.json
is accessible and properly formatted - Check that your app's SHA256 fingerprint matches the one in
assetlinks.json
- Ensure the domain in AndroidManifest.xml matches your proxy domain exactly
Story Not Opening
- Verify the story handle exists and is published
- Check if the story is available in the widget you're trying to open it from
- Use direct launch (
PreviewStoryByHandleLauncher
) as a fallback
URL Parsing Issues
- Test with different URL patterns your backend might generate
- Add logging to debug which path segments contain the handle
- Handle edge cases like URLs with query parameters