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.

Android Configuration

1. Update AndroidManifest.xml

Add an intent filter to your main activity (or dedicated deep link activity) to handle incoming links:

AndroidManifest.xml
<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:

assetlinks.json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.YOUR_PACKAGE",
"sha256_cert_fingerprints": [
"YOUR_PACKAGE_FINGERPRINT"
]
}
}
]
Getting your SHA256 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:

MainActivity.kt
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:

MainActivity.kt
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 PatternHandle Extraction
https://domain.com/story-handlepathSegments[0]
https://domain.com/story/story-handlepathSegments[1]
https://domain.com/stories/story-handlepathSegments[1]
https://domain.com/content/story-handlepathSegments[1]

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

  1. Use Direct Launch for Shared Content: PreviewStoryByHandleLauncher is more reliable for shared links as it doesn't depend on widget loading
  2. Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
  3. Handle Multiple URL Patterns: Make your URL parsing robust to handle different backend configurations
  4. Validate Handles: Check if handles exist before attempting to open stories
  5. User Experience: Show loading states while processing deep links
  6. Error Handling: Gracefully handle invalid or expired story handles

Common Issues

  • 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