Skip to content

Working with Users#

Storyteller personalises content, tracking, and follow state per end user. The Flutter SDK mirrors the native capabilities exposed by the iOS and Android libraries.

Installation uses Flutter 12.2.0 (Android 12.2.0 / iOS 11.9.1). The native behavior audit below used an earlier 12.1.2 development snapshot (Android 12.1.2 / iOS 11.8.0), compared with published 11.6.5 (Android 11.6.4 / iOS 11.6.1). These historical audit pins are not the released dependencies; the instruction-reset comparison is version-specific. See Changelog when comparing releases.

Associate a user during initialisation#

Provide an externalId when calling Storyteller.initialize. This identifier usually maps to your own user record.

final result = await Storyteller.initialize(
  'YOUR_API_KEY',
  externalId: currentUser.id,
);

If you also need to configure tracking behavior for privacy compliance, pass eventTrackingOptions during initialization. See Privacy and Tracking for details.

The Showcase StorytellerService.initialize persists the API key and externalId, then reuses them on relaunch—mirror that pattern if you need automatic reconnection.

Change the signed-in user#

Flutter exposes identity through initialize, with no separate user-switch method. Reinitialize with the new externalId and the intended tracking options, check result.success, then reapply app-managed attributes/follows and reload or recreate your views. Omitting externalId selects a native guest/default identity.

With remote viewing enabled, switching identity clears user-specific read/view status, poll/quiz answers, likes, custom attributes (including locale), followed categories, pending analytics and stored mute state. These values are not transferred to the new user. iOS additionally clears Cards viewed/tapped state and content caches; Android clears its status stores and Clips feed cache. Flutter 11.6.5 (iOS 11.6.1) resets instructional completion on a user change; Flutter 12.1.2 (iOS 11.8.0) retains that installation-level state. When remote viewing is disabled, do not assume the same user-switch/reset behavior: iOS does not retain an identity to compare.

Update custom attributes#

Use key/value attributes to pass additional segmentation information into Storyteller. Attributes are persisted natively until removed, replaced, or cleared by a user change. Their use in requests depends on enablePersonalization. Android's customAttributes() returns an empty map while personalization is disabled; iOS still returns stored attributes.

await Storyteller.setCustomAttribute('subscription-tier', 'gold');
await Storyteller.setCustomAttribute('favorite-team', 'city-fc');

final attributes = await Storyteller.customAttributes();
debugPrint('Current attributes: $attributes');

await Storyteller.removeCustomAttribute('favorite-team');

Use setCustomAttributes for one Flutter bridge operation that replaces the current readable attribute map. An empty map clears that map. Android hides stored attributes while personalization is disabled, so replacement cannot remove those hidden values in that mode.

await Storyteller.setCustomAttributes({
  'subscription-tier': 'gold',
  'favorite-team': 'city-fc',
});

iOS forwards this to its native batch API. Android 11.6.4 and 12.1.2 expose only per-attribute primitives, so the Flutter bridge sets changed values and removes omitted prior keys synchronously within the same method call. If an Android replacement fails, the bridge restores the prior map when possible and throws a PlatformException with code CUSTOM_ATTRIBUTES_ERROR.

Toggle personalization controls the same way the Showcase AttributeService._addValue calls setCustomAttribute, setLocale, or addFollowedCategory depending on the attribute type.

Handle locale changes#

If your app allows users to switch language independently from the device, inform Storyteller by overriding the locale:

await Storyteller.setLocale('fr');

// Clear the override and return locale selection to the native SDK.
await Storyteller.setLocale(null);

Use an ISO language code such as fr or en for cross-platform locale overrides. iOS lowercases valid ISO language codes and ignores unsupported codes such as fr-FR. Use setLocale for locale changes: iOS ignores the reserved stlocale key passed to custom-attribute setters. iOS also lowercases custom-attribute keys, while Android preserves them; use lowercase keys consistently. Replacing the attribute map on iOS removes a prior locale override.

Follow categories explicitly#

Confirm your tenant is configured for app-managed following before using mutation methods; native SDKs ignore these app-managed changes when Storyteller manages following. Manipulate follow state in response to profile changes, onboarding, or saved preferences:

await Storyteller.addFollowedCategories(['travel', 'music']);

final isSportsFollowed = await Storyteller.isCategoryFollowed('sports');
if (!isSportsFollowed) {
  await Storyteller.addFollowedCategory('sports');
}

await Storyteller.removeFollowedCategory('travel');
final followed = await Storyteller.followedCategories();

You can also remove several categories at once or replace the whole set:

await Storyteller.removeFollowedCategories(['travel', 'music']);
await Storyteller.setFollowedCategories(['sports', 'news']);

The add/remove methods start asynchronous native changes; their Flutter Future does not wait for category resolution, so an immediate read can still return old state. setFollowedCategories waits for native category resolution (when app-managed following is enabled). If some IDs are unknown, the native SDK applies the subset it can resolve. If that request fails, the Future throws and the previous followed-category state is kept.

getFollowableCategories() returns StorytellerFollowableCategories.categories, a flat list of values with id, isFollowed, and optional name, displayTitle, externalId, type, thumbnailUrl, and placement (title/code). Use it for a category-management UI rather than inferring metadata from follow-event payloads. For state or loading problems, see Troubleshooting.