State Layer

Every object the SDK hands you that holds data (a feed, an activity, a list) owns its state and keeps it current. You read the state, and the SDK updates it as responses arrive and WebSocket events come in. You do not merge responses yourself.

Reading state

Each of these objects exposes the same three accessors:

Accessor Type Use
state the state object The current value, read synchronously
stream Stream<T> Every subsequent value
notifier StateNotifier<T> For the Flutter builders below
final feed = client.feed(group: 'user', id: 'john');
await feed.getOrCreate();

// The current value, read synchronously
final activities = feed.state.activities;

// A stream of every subsequent value
final subscription = feed.stream.listen((state) {
  print('${state.activities.length} activities');
});

// Or the StateNotifier itself, for the Flutter builders below
final notifier = feed.notifier;

await subscription.cancel();
feed.dispose();
Note:

Call dispose() on a feed, activity or list when you are done with it. This is separate from StreamFeedsClient.dispose(), which is terminal and releases the whole client. Do not call that from a widget's dispose.

FeedState

Populated by getOrCreate() and kept current from then on.

Field Type Description
fid FeedId The feed this state belongs to
feedQuery FeedQuery The query the feed was created from
feed FeedData? The feed's own data: name, description, counts, location
activities List<ActivityData> The activities, in the order the feed returned them
aggregatedActivities List<AggregatedActivityData> The groups, for an aggregated feed
followers List<FollowData> Follows where this feed is the target
following List<FollowData> Follows where this feed is the source
followRequests List<FollowData> Pending requests to follow this feed
members List<FeedMemberData> The feed's members
pinnedActivities List<ActivityPinData> The pinned activities
notificationStatus NotificationStatusResponse? Unread and unseen counts, for a notification feed
activitiesPagination PaginationData? The cursors for the activity list
membersPagination PaginationData? The cursors for the member list
canLoadMoreActivities bool Derived from activitiesPagination
canLoadMoreMembers bool Derived from membersPagination
final feed = client.feed(group: 'user', id: 'john');
await feed.getOrCreate();

final state = feed.state;
final title = state.feed?.name ?? state.fid.rawValue;

for (final activity in state.activities) {
  print('$title: ${activity.text}');
}

// `canLoadMoreActivities` and `canLoadMoreMembers` are derived from the
// pagination cursors, so you never have to read those yourself
if (state.canLoadMoreActivities) {
  await feed.queryMoreActivities(limit: 10);
}

ActivityState

Field Type Description
activity ActivityData? The activity itself, null until get() returns
comments List<CommentData> Its comments
commentsPagination PaginationData? The cursors for the comment list
canLoadMoreComments bool Derived from commentsPagination
final activity = client.activity(
  activityId: 'activity_123',
  fid: const FeedId.user('john'),
);
await activity.get();

// `activity` is null until `get()` returns
final state = activity.state;
print(state.activity?.text);

for (final comment in state.comments) {
  print('${comment.user.id}: ${comment.text}');
}

if (state.canLoadMoreComments) {
  await activity.queryMoreComments(limit: 10);
}

activity.dispose();
Info:

There is no standalone poll state in the Flutter SDK. A poll travels on ActivityData.poll, and PollList covers querying polls across activities.

Enum-like fields are Strings

The models you read off state use extension types over String for their enum-like fields: ActivityData.visibility, FeedData.visibility, CommentData.status, FeedMemberData.status, FollowData.status and pushPreference, ActivityData.restrictReplies.

They are not Dart enums, so there is no .values, no .name, no .index, and no catch-all member. A value the server adds later arrives verbatim rather than collapsing to something unknown, which means a switch over one is never exhaustive and always needs a default arm.

await feed.getOrCreate();

for (final activity in feed.state.activities) {
  // `visibility` and `visibilityTag` are two separate fields: a `tag:premium`
  // activity has `visibility` of `tag` and `visibilityTag` of `premium`.
  print('${activity.visibility} / ${activity.visibilityTag}');

  // The visibility values are extension types over String, not a Dart enum, so
  // an unrecognized value the server adds later arrives verbatim rather than
  // collapsing to a catch-all. A switch over one always needs a default arm.
  final label = switch (activity.visibility) {
    ActivityDataVisibility.public => 'Everyone',
    ActivityDataVisibility.private => 'Only me',
    ActivityDataVisibility.tag => 'Tagged: ${activity.visibilityTag}',
    _ => 'Unrecognized: ${activity.visibility}',
  };
  print(label);
}

Because they implement String, comparing one against a plain string works, and so does using one anywhere a String is expected.

List state

The paginated queries all share one shape: build one from a query, get() the first page, queryMore…() for the next, and read the results off state.

// Every list API has the same shape: create it from a query, call `get()` for
// the first page, then `queryMore…` for the next.
final feedList = client.feedList(
  FeedsQuery(
    filter: Filter.equal(FeedsFilterField.createdById, 'john'),
    limit: 10,
  ),
);

await feedList.get();

for (final feed in feedList.state.feeds) {
  print(feed.name);
}

if (feedList.state.canLoadMore) {
  await feedList.queryMoreFeeds(limit: 10);
}

feedList.dispose();

These are the list objects, each created from the client:

Object Created with Holds
ActivityList client.activityList activities
ActivityCommentList client.activityCommentList comments, threaded
ActivityReactionList client.activityReactionList reactions
BookmarkList client.bookmarkList bookmarks
BookmarkFolderList client.bookmarkFolderList bookmarkFolders
CommentList client.commentList comments
CommentReactionList client.commentReactionList reactions
CommentReplyList client.commentReplyList replies
FeedList client.feedList feeds
FollowList client.followList follows
MemberList client.memberList members
ModerationConfigList client.moderationConfigList configs
PollList client.pollList polls
PollVoteList client.pollVoteList votes
UserList client.userList users

Querying users

UserList is the query over users. Unlike the others it paginates with limit and offset rather than cursors.

// Users are paginated with limit/offset rather than cursors
final userList = client.userList(
  UsersQuery(
    filter: Filter.autoComplete(UsersFilterField.name, 'Al'),
    sort: [UsersSort.asc(UsersSortField.name)],
    limit: 25,
  ),
);

await userList.get();

while (userList.state.canLoadMore) {
  final result = await userList.queryMoreUsers();
  if (result.isFailure) break;
}

for (final user in userList.state.users) {
  print('${user.name ?? user.id} is ${user.online ? 'online' : 'offline'}');
}

userList.dispose();

Using state in Flutter

notifier is a StateNotifier, so it works with flutter_state_notifier's builders and providers.

// Wiring a feed's state into the widget tree with `flutter_state_notifier`,
// which is a separate dependency:
//
//   dependencies:
//     flutter_state_notifier: ^1.0.0
class FeedActivities extends StatelessWidget {
  const FeedActivities({super.key, required this.feed});

  final Feed feed;

  @override
  Widget build(BuildContext context) {
    return StateNotifierBuilder<FeedState>(
      stateNotifier: feed.notifier,
      builder: (context, state, child) {
        return ListView.builder(
          itemCount: state.activities.length,
          itemBuilder: (context, index) {
            final activity = state.activities[index];
            return Text(activity.text ?? '');
          },
        );
      },
    );
  }
}

stream works with a plain StreamBuilder if you would rather not add the dependency.

Add Chat to my app: getstream.io/SKILL.md

The fastest way to build with Stream. Start a new project or improve an existing one. Full CLI and documentation integration out of the box.


Ask your agent:

/stream Build me a Social App with Feeds and Moderation.
/stream Any livestream calls running?
/stream Feeds Flutter v3: <Your Question>