The Flutter SDK uses Dart Streams to deliver Storyteller events to your app. You subscribe to event streams provided by the Storyteller class to handle events throughout your app. This stream-based approach replaces the delegate pattern used in native iOS and Android SDKs.
Subscribe before the interaction you need to observe and keep each StreamSubscription so you can cancel it when its owner is disposed. These broadcast streams deliver live events; do not use them as a history or initialization-success signal.
You can subscribe to the following optional event streams to handle Storyteller events:
Need a full reference implementation? The Showcase StorytellerService._setupEventListeners demonstrates subscription and routing patterns and forwards the payloads to its router/logging hooks.
The onUserActivityOccurred stream emits events when analytics events are triggered within the SDK. This allows your app to observe and potentially forward these events to your own analytics systems. Delivery depends on enableUserActivityTracking, ad tracking gates and metadata-redaction choices. Native sdkInitialized is internal and is not delivered on this stream. See the Analytics page for details on event types and data.
Type:Stream<StorytellerUserActivityEvent>
Example:
Storyteller.onUserActivityOccurred.listen((event){print('Activity: ${event.type}');print('Data: ${event.data}');// Forward to your analytics system});
The userNavigatedToApp stream emits URLs when a user presses an action button on a page which should direct them to a specific place within your app.
Type:Stream<String>
Example:
Storyteller.userNavigatedToApp.listen((url){print('Navigate to: $url');// Handle navigation using your app's routing system// Example: Navigator.pushNamed(context, url);});
See Deep Linking for deep link handling patterns and how to route these URLs.
The categoryFollowActionTaken stream emits events when a user follows or unfollows a category of clips (the category can represent a player, a team, etc.) from within the SDK's UI.
Type:Stream<CategoryFollowEvent>
The CategoryFollowEvent contains:
- category - A StorytellerCategory containing externalId, type, optional placement, name, and displayTitle
- isFollowing - A boolean value indicating whether the user is following or unfollowing the specified category
Example:
Storyteller.categoryFollowActionTaken.listen((event){print('Category ${event.category.name} followed: ${event.isFollowing}');if(event.isFollowing){// Add the category to your list of followed categories}else{// Remove the category from your list of followed categories}});
The onShareButtonTapped stream emits share payloads when custom share handling is enabled. Call Storyteller.setUseCustomShareHandling(true) before presenting Storyteller content, then show your own share UI when the stream emits.
Type:Stream<StorytellerShareEvent>
The StorytellerShareEvent contains:
- text - The share text generated by Storyteller
- title - The share title, or an empty string when Storyteller provides none
- url - The share URL, or an empty string when Storyteller provides none
Example:
awaitStoryteller.setUseCustomShareHandling(true);Storyteller.onShareButtonTapped.listen((event){print('Share title: ${event.title}');print('Share url: ${event.url}');// Present your own share flow, then call Storyteller.resumePlayer().});
The onPlayerPresented stream emits an event when a story or clip player is about to be presented on screen. On iOS, this can be useful for pausing background media. The current Android Flutter bridge exposes the stream but does not emit presentation events; use list onTileTapped callbacks for list-triggered opens and Storyteller.isPlayerVisible() when you need to query visibility.
Type:Stream<void>
Example:
Storyteller.onPlayerPresented.listen((_){print('Player is being presented');// Pause your app's media playback});
The onPlayerDismissed stream emits an event when a story or clip player has been dismissed from screen. iOS emits this global event. The current Android Flutter bridge does not emit it; its Stories/Clips list widgets still expose onPlayerDismissed callbacks. Use those callbacks for list-owned playback and query Storyteller.isPlayerVisible() for visibility.
Type:Stream<void>
Example:
Storyteller.onPlayerDismissed.listen((_){print('Player was dismissed');// Resume your app's media playback// audioPlayer.resume();});
The onLog stream emits debug log messages from the Storyteller SDK. iOS emits these messages. The current Android Flutter bridge exposes this stream but does not emit messages; inspect native Android logs instead.
Type:Stream<String>
Example:
Storyteller.onLog.listen((message){print('Storyteller SDK Log: $message');// Forward to your logging system});
Stream<StorytellerOpenError> reports sanitized programmatic Clips-open failures. Subscribe before calling openCollection, openCollectionWithCategory, or openClipByExternalId; supply a requestId when correlation matters. The event contains requestId, operation (collection, collectionWithCategory, clipByExternalId, or unknown), and code (contentNotFound, clipNotFound, network, or unknown). It contains no raw error message. Catch the open call's Future failures as well: Android callback-style errors can arrive after that Future completes. See Additional Methods.
For handling events specific to StorytellerStoriesRowView and StorytellerStoriesGridView widgets, please refer to the StorytellerListViews documentation for details on widget-specific callbacks like:
- onDataLoadStarted
- onDataLoadComplete
- onTileTapped
- onPlayerDismissed
{"slug": "storyteller-delegate", "page_title": "Event Streams", "page_url": "StorytellerDelegate/", "canonical_url": "/flutter/StorytellerDelegate/", "markdown": "# Implementing Storyteller Delegate Callbacks\n\nThe Flutter SDK uses Dart `Stream`s to deliver Storyteller events to your app. You subscribe to event streams provided by the `Storyteller` class to handle events throughout your app. This stream-based approach replaces the delegate pattern used in native iOS and Android SDKs.\n\n## Available Event Streams\n\nSubscribe before the interaction you need to observe and keep each `StreamSubscription` so you can cancel it when its owner is disposed. These broadcast streams deliver live events; do not use them as a history or initialization-success signal.\n\nYou can subscribe to the following optional event streams to handle Storyteller events:\n\nNeed a full reference implementation? The Showcase [`StorytellerService._setupEventListeners`](https://github.com/getstoryteller/storyteller-showcase-flutter/blob/main/lib/services/storyteller_service.dart#L239) demonstrates subscription and routing patterns and forwards the payloads to its router/logging hooks.\n\n### onUserActivityOccurred\n\nThe `onUserActivityOccurred` stream emits events when analytics events are triggered within the SDK. This allows your app to observe and potentially forward these events to your own analytics systems. Delivery depends on `enableUserActivityTracking`, ad tracking gates and metadata-redaction choices. Native `sdkInitialized` is internal and is not delivered on this stream. See the [Analytics](Analytics.md) page for details on event types and data.\n\n**Type:** `Stream<StorytellerUserActivityEvent>`\n\n**Example:**\n```dart\nStoryteller.onUserActivityOccurred.listen((event) {\n print('Activity: ${event.type}');\n print('Data: ${event.data}');\n // Forward to your analytics system\n});\n```\n\n### userNavigatedToApp\n\nThe `userNavigatedToApp` stream emits URLs when a user presses an action button on a page which should direct them to a specific place within your app. \n\n**Type:** `Stream<String>`\n\n**Example:**\n```dart\nStoryteller.userNavigatedToApp.listen((url) {\n print('Navigate to: $url');\n // Handle navigation using your app's routing system\n // Example: Navigator.pushNamed(context, url);\n});\n```\n\nSee [Deep Linking](Deeplinking.md) for deep link handling patterns and how to route these URLs.\n\n### categoryFollowActionTaken\n\nThe `categoryFollowActionTaken` stream emits events when a user follows or unfollows a category of clips (the category can represent a player, a team, etc.) from within the SDK's UI.\n\n**Type:** `Stream<CategoryFollowEvent>`\n\nThe `CategoryFollowEvent` contains:\n- `category` - A `StorytellerCategory` containing `externalId`, `type`, optional `placement`, `name`, and `displayTitle`\n- `isFollowing` - A boolean value indicating whether the user is following or unfollowing the specified category\n\n**Example:**\n```dart\nStoryteller.categoryFollowActionTaken.listen((event) {\n print('Category ${event.category.name} followed: ${event.isFollowing}');\n \n if (event.isFollowing) {\n // Add the category to your list of followed categories\n } else {\n // Remove the category from your list of followed categories\n }\n});\n```\n\n### onShareButtonTapped\n\nThe `onShareButtonTapped` stream emits share payloads when custom share handling is enabled. Call `Storyteller.setUseCustomShareHandling(true)` before presenting Storyteller content, then show your own share UI when the stream emits.\n\n**Type:** `Stream<StorytellerShareEvent>`\n\nThe `StorytellerShareEvent` contains:\n- `text` - The share text generated by Storyteller\n- `title` - The share title, or an empty string when Storyteller provides none\n- `url` - The share URL, or an empty string when Storyteller provides none\n\n**Example:**\n```dart\nawait Storyteller.setUseCustomShareHandling(true);\n\nStoryteller.onShareButtonTapped.listen((event) {\n print('Share title: ${event.title}');\n print('Share url: ${event.url}');\n // Present your own share flow, then call Storyteller.resumePlayer().\n});\n```\n\n### onPlayerPresented\n\nThe `onPlayerPresented` stream emits an event when a story or clip player is about to be presented on screen. On iOS, this can be useful for pausing background media. The current Android Flutter bridge exposes the stream but does not emit presentation events; use list `onTileTapped` callbacks for list-triggered opens and `Storyteller.isPlayerVisible()` when you need to query visibility.\n\n**Type:** `Stream<void>`\n\n**Example:**\n```dart\nStoryteller.onPlayerPresented.listen((_) {\n print('Player is being presented');\n // Pause your app's media playback\n});\n```\n\n### onPlayerDismissed\n\nThe `onPlayerDismissed` stream emits an event when a story or clip player has been dismissed from screen. iOS emits this global event. The current Android Flutter bridge does not emit it; its Stories/Clips list widgets still expose `onPlayerDismissed` callbacks. Use those callbacks for list-owned playback and query `Storyteller.isPlayerVisible()` for visibility.\n\n**Type:** `Stream<void>`\n\n**Example:**\n```dart\nStoryteller.onPlayerDismissed.listen((_) {\n print('Player was dismissed');\n // Resume your app's media playback\n // audioPlayer.resume();\n});\n```\n\n### onLog\n\nThe `onLog` stream emits debug log messages from the Storyteller SDK. iOS emits these messages. The current Android Flutter bridge exposes this stream but does not emit messages; inspect native Android logs instead.\n\n**Type:** `Stream<String>`\n\n**Example:**\n```dart\nStoryteller.onLog.listen((message) {\n print('Storyteller SDK Log: $message');\n // Forward to your logging system\n});\n```\n\n### onClipsOpenError\n\n`Stream<StorytellerOpenError>` reports sanitized programmatic Clips-open failures. Subscribe before calling `openCollection`, `openCollectionWithCategory`, or `openClipByExternalId`; supply a `requestId` when correlation matters. The event contains `requestId`, `operation` (`collection`, `collectionWithCategory`, `clipByExternalId`, or `unknown`), and `code` (`contentNotFound`, `clipNotFound`, `network`, or `unknown`). It contains no raw error message. Catch the open call's `Future` failures as well: Android callback-style errors can arrive after that `Future` completes. See [Additional Methods](AdditionalMethods.md#navigation-helpers).\n\n## StorytellerListView Delegate\n\nFor handling events specific to `StorytellerStoriesRowView` and `StorytellerStoriesGridView` widgets, please refer to the [StorytellerListViews](StorytellerListViews.md) documentation for details on widget-specific callbacks like:\n- `onDataLoadStarted`\n- `onDataLoadComplete`\n- `onTileTapped`\n- `onPlayerDismissed`\n\nFor callbacks that do not arrive, check platform availability above and the [Troubleshooting guide](Troubleshooting.md). Analytics delivery also depends on [Privacy and Tracking](PrivacyAndTracking.md).\n", "copy_markdown_include_header": false, "base_path": "", "ai_dir": "ai", "missing_payload_behavior": "empty"}