Stories are activities with an expiration date, usually used to share information that is available for a limited amount of time. Most often there is a dedicated feed for users to post expiring activities, and there is a separate timeline for reading stories.
If you prefer to create your own story feed groups, you can do so with custom feed groups.
Creating or updating feed groups is only possible server-side.
The following example defines two feed groups:
my-story for users to post their stories to
my-stories for users to follow others' stories
There are two settings specific to story feed groups:
You can enable tracking watched activities: this makes it possible to track which stories a user has seen or not (please note that this is different from notification configurations, typically you don't need that for stories)
You can decide if watched stories should be removed from the feed. It's false for the built-in story groups
await serverClient.feeds.createFeedGroup({ id: `my-story`, stories: { skip_watched: false, track_watched: true, },});await serverClient.feeds.createFeedGroup({ id: `my-stories`, stories: { skip_watched: false, track_watched: true, }, aggregation: { // Typically stories are aggregated by user id format: "{{ user_id }}", }, activity_selectors: [{ type: "following" }],});
$feedsClient->createFeedGroup( new GeneratedModels\CreateFeedGroupRequest( id: 'my-story', stories: new GeneratedModels\StoriesConfig( skipWatched: false, trackWatched: true ) ));$feedsClient->createFeedGroup( new GeneratedModels\CreateFeedGroupRequest( id: 'my-stories', stories: new GeneratedModels\StoriesConfig( skipWatched: false, trackWatched: true ), aggregation: new GeneratedModels\AggregationConfig( format: '{{ user_id }}' ), activitySelectors: [ ['type' => 'following'] ] ));
const saraStoryTimeline = client.feed("stories", "sara");await saraStoryTimeline.getOrCreate({ watch: true });// Since story timeline is aggregated by user id, we read aggregated activitiesconst johnStories = saraStoryTimeline.state.getLatestValue().aggregated_activities[0];// True if we watched all active stories of a userconsole.log(johnStories.is_watched);// Display all of John's active storiesjohnStories.activities.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as readawait saraStoryTimeline.markActivity({ mark_watched: [johnStories.activities[0].id],});
const saraStoryTimeline = client.feed("stories", "sara");await saraStoryTimeline.getOrCreate({ watch: true });const { aggregated_activities } = useAggregatedActivities(saraStoryTimeline) ?? {};// Since story timeline is aggregated by user id, we read aggregated activitiesconst johnStories = aggregated_activities[0];// True if we watched all active stories of a userconsole.log(johnStories.is_watched);// Display all of John's active storiesjohnStories.activities.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as readawait saraStoryTimeline.markActivity({ mark_watched: [johnStories.activities[0].id],});
const saraStoryTimeline = client.feed("stories", "sara");await saraStoryTimeline.getOrCreate({ watch: true });const { aggregated_activities } = useAggregatedActivities(saraStoryTimeline) ?? {};// Since story timeline is aggregated by user id, we read aggregated activitiesconst johnStories = aggregated_activities[0];// True if we watched all active stories of a userconsole.log(johnStories.is_watched);// Display all of John's active storiesjohnStories.activities.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as readawait saraStoryTimeline.markActivity({ mark_watched: [johnStories.activities[0].id],});
const saraStoryTimeline = client.feeds.feed("stories", "sara");const response = await saraStoryTimeline.getOrCreate({ user_id: "sara" });// Since story timeline is aggregated by user id, we read aggregated activitiesconst johnStories = response.aggregated_activities[0];// True if we watched all active stories of a userconsole.log(johnStories.is_watched);// Display all of John's active storiesjohnStories.activities.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as readawait saraStoryTimeline.markActivity({ mark_watched: [johnStories.activities[0].id], user_id: "sara",});
$saraStoryTimeline = $feedsClient->feed('stories', 'sara');$response = $saraStoryTimeline->getOrCreateFeed( new GeneratedModels\GetOrCreateFeedRequest(userID: 'sara'));// Since story timeline is aggregated by user id, we read aggregated activities$johnStories = $response->getData()->aggregatedActivities[0];// True if we watched all active stories of a userecho $johnStories->isWatched ? 'true' : 'false';// Display all of John's active storiesforeach ($johnStories->activities as $activity) { // True if we watched a given story echo $activity->isWatched ? 'true' : 'false';}// Mark a story as read$saraStoryTimeline->markActivity( new GeneratedModels\MarkActivityRequest( markWatched: [$johnStories->activities[0]->id], userID: 'sara' ));
Story groups in story timelines are sorted by unread count. Watched story groups are returned last (unless the feed group is configured not to return watched stories).
When marking an activity as watched, feeds.stories_feed.updated WebSocket event is dispatched to clients of the user who marked the activity (Sara in this example). There are no WebSocket events sent when a user adds to their story, you need to reread the feed to get updates.
While there is no limit of how many active stories a user can have, aggregated_activities return the latest 100 activities for each group.
Pagination for story groups work the same way as for any other feeds. This is how you can load the next page of story groups:
val feed = client.feed( query = FeedQuery( group = "user", id = "john", activityLimit = 10 ))// Page 1feed.getOrCreate()val activities = feed.state.activities // First 10 activities// Page 2val page2Activities: Result<List<ActivityData>> = feed.queryMoreActivities(limit = 10)val page1And2Activities = feed.state.activities
const feed = client.feed("user", "jack");// First pageawait feed.getOrCreate({ limit: 10,});// Second pageawait feed.getNextPage();console.log(feed.state.getLatestValue().is_loading_activities);// Truthy if feed has next pageconsole.log(feed.state.getLatestValue().next);console.log(feed.state.getLatestValue().activities);// Only if feed group has aggregation turned onconsole.log(feed.state.getLatestValue().aggregated_activities);
const feed = client.feed("user", "jack");// First pageawait feed.getOrCreate({ limit: 10,});const { activities, loadNextPage, is_loading, has_next_page } = useFeedActivities(feed) ?? {};// Only if feed group has aggregation turned onconst { aggregated_activities, is_loading, has_next_page } = useAggregatedActivities(feed) ?? {};
const feed = client.feed("user", "jack");// First pageawait feed.getOrCreate({ limit: 10,});const { activities, loadNextPage, is_loading, has_next_page } = useFeedActivities(feed) ?? {};// Only if feed group has aggregation turned onconst { aggregated_activities, is_loading, has_next_page } = useAggregatedActivities(feed) ?? {};
Following someone's story feed is one way to read stories. However, it's also possible to read someone's story feed directly:
// Sara reads John's story feedconst johnStoryFeed = saraClient.feed("story", "john", { // By default new activities are added to the start of the list, but this is not what we want for stories addNewActivitiesTo: "end",});// Alternatively set after feed is createdjohnStoryFeed.addNewActivitiesTo = "end";await johnStoryFeed.getOrCreate({ watch: true, limit: 100,});const johnStories = johnStoryFeed.state.getLatestValue().activities;// Display all of John's active storiesjohnStories.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as watchedawait johnStoryFeed.markActivity({ mark_watched: [johnStories[0].id],});
// Sara reads John's story feedconst johnStoryFeed = saraClient.feed("story", "john", { // By default new activities are added to the start of the list, but this is not what we want for stories addNewActivitiesTo: "end",});// Alternatively set after feed is createdjohnStoryFeed.addNewActivitiesTo = "end";await johnStoryFeed.getOrCreate({ watch: true, limit: 100,});const { activities: johnStories, is_loading, has_next_page, loadNextPage,} = useFeedActivities(feed) ?? {};// Display all of John's active storiesjohnStories.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as watchedawait johnStoryFeed.markActivity({ mark_watched: [johnStories[0].id],});
// Sara reads John's story feedconst johnStoryFeed = saraClient.feed("story", "john", { // By default new activities are added to the start of the list, but this is not what we want for stories addNewActivitiesTo: "end",});// Alternatively set after feed is createdjohnStoryFeed.addNewActivitiesTo = "end";await johnStoryFeed.getOrCreate({ watch: true, limit: 100,});const { activities: johnStories, is_loading, has_next_page, loadNextPage,} = useFeedActivities(feed) ?? {};// Display all of John's active storiesjohnStories.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as watchedawait johnStoryFeed.markActivity({ mark_watched: [johnStories[0].id],});
const johnStoryFeed = client.feeds.feed("story", "john");const response = await johnStoryFeed.getOrCreate({ limit: 100, user_id: "john",});const johnStories = response.activities;johnStories.forEach((activity) => { // True if we watched a given story console.log(activity.is_watched);});// Mark a story as watchedawait johnStoryFeed.markActivity({ mark_watched: [johnStories[0].id], user_id: "sara",});
// Sara reads John's story feed$johnStoryFeed = $feedsClient->feed('story', 'john');$response = $johnStoryFeed->getOrCreateFeed( new GeneratedModels\GetOrCreateFeedRequest( userID: 'john', limit: 100 ));$johnStories = $response->getData()->activities;// Display all of John's active storiesforeach ($johnStories as $activity) { // True if we watched a given story echo $activity->isWatched ? 'true' : 'false';}// Mark a story as watched$johnStoryFeed->markActivity( new GeneratedModels\MarkActivityRequest( markWatched: [$johnStories[0]->id], userID: 'sara' ));
When marking an activity as watched, feeds.stories_feed.updated WebSocket event is dispatched to clients of the user who marked the activity (Sara in this example). If a new story is added to the feed, a feeds.activity.added event is dispatched.