StreamThreadListView

StreamThreadListView is a widget that shows an overview of all threads the current user participated in.

Each thread tile displays:

  • The user avatar of the thread's parent message author
  • The channel name the thread belongs to
  • A preview of the parent message that started the thread
  • A footer with participant avatars, reply count, and a relative timestamp
  • An unread badge when there are unread replies

The list is paginated by default, and only the most recently updated threads are loaded initially. Older threads are loaded only when the user scrolls to the end of the thread list.

Usage

The widget is backed by a controller named StreamThreadListController, which is responsible for loading the threads and managing the state of the widget.

late final _threadListController = StreamThreadListController(
  limit: 30,
  client: StreamChat.of(context).client,
  options: ThreadOptions(
    replyLimit: 2,
    memberLimit: 100,
    participantLimit: 100,
  ),
);

The StreamThreadListController has a few properties that can be used to customize the behavior of the widget:

  • limit: The maximum number of threads to load per page (default: defaultThreadsPagedLimit = 10).
  • ThreadOptions.replyLimit: The maximum number of (latest) replies to load per thread.
  • ThreadOptions.memberLimit: The maximum number of members to load per thread.
  • ThreadOptions.participantLimit: The maximum number of participants to load per thread.

Once the controller is created, it can be passed to the StreamThreadListView widget.

StreamThreadListView(
  controller: _threadListController,
);

This will create a paginated list of threads, which will load more threads as the user scrolls to the end of the list.

No threadsThread list
Empty
Loaded

Unread Threads

While the user is viewing the thread list, and a new thread is created, or a thread which is not yet loaded is updated, we can show a banner informing the user about the number of new threads. The user can then tap the banner to reload the thread list and load the newly updated threads.

To implement this feature, wrap StreamThreadListView with StreamUnreadThreadsBanner. The banner acts as a wrapper (similar to how RefreshIndicator wraps a scrollable) and appears above the child when enabled.

ValueListenableBuilder<Set<String>>(
  valueListenable: _threadListController.unseenThreadIds,
  builder: (context, unseenThreadIds, child) => StreamUnreadThreadsBanner(
    enabled: unseenThreadIds.isNotEmpty,
    unreadThreads: unseenThreadIds,
    onRefresh: () async {
      await _threadListController.refresh(resetValue: false);
      // Clear the list of unseen threads once the threads are refreshed.
      _threadListController.clearUnseenThreadIds();
    },
    child: child!,
  ),
  child: StreamThreadListView(
    controller: _threadListController,
  ),
);

When onRefresh is in progress, the banner automatically shows a loading spinner with a "Loading..." label instead of the refresh icon and thread count. The banner is disabled for taps while loading.

Unread Threads

Handling Thread Taps

To handle taps on threads, we can use the onThreadTap callback provided by the StreamThreadListView widget.

StreamThreadListView(
  controller: _threadListController,
  onThreadTap: (thread) {
    // Navigate to thread conversation
  },
);

We can also handle long presses on threads using the onThreadLongPress callback.

StreamThreadListView(
  controller: _threadListController,
  onThreadLongPress: (thread) {
    // Handle the long press on the thread.
  },
);

Customizing Thread Items

Using itemBuilder

You can customize individual thread items using the itemBuilder parameter:

StreamThreadListView(
  controller: _threadListController,
  itemBuilder: (context, threads, index, defaultWidget) {
    final thread = threads[index];
    // Return your custom widget here.
  },
);

Custom Thread ListTile

Using StreamComponentFactory

For app-wide customization of thread tiles, register a custom builder through StreamComponentFactory. This replaces the default StreamThreadListTile rendering for every StreamThreadListView in your app.

StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      threadListItem: (context, props) {
        // props is StreamThreadListTileProps containing thread, currentUser, onTap, onLongPress
        return MyCustomThreadTile(
          thread: props.thread,
          onTap: props.onTap,
        );
      },
    ),
  ),
  child: MyApp(),
)

The StreamThreadListTileProps class contains:

PropertyTypeDescription
threadThreadThe thread displayed by the tile
currentUserUser?The current user
onTapGestureTapCallback?Called when the tile is tapped
onLongPressGestureLongPressCallback?Called when the tile is long pressed

Theming

You can customize the look of all thread tiles using StreamThreadListTileThemeData via StreamChatThemeData.threadListTileTheme.

When a theme property is null, the tile falls back to computed defaults derived from the ambient StreamTextTheme and StreamColorScheme resolved from the current StreamTheme extension.

StreamChat(
  client: client,
  themeData: StreamChatThemeData(
    threadListTileTheme: StreamThreadListTileThemeData(
      backgroundColor: Colors.white,
      threadChannelNameStyle: TextStyle(fontWeight: FontWeight.bold),
      threadReplyToMessageStyle: TextStyle(color: Colors.grey),
      threadReplyCountStyle: TextStyle(color: Colors.blue),
      threadLatestReplyTimestampStyle: TextStyle(color: Colors.grey),
      threadLatestReplyTimestampFormatter: (context, date) {
        return '${date.hour}:${date.minute}';
      },
      threadUnreadMessageCountStyle: TextStyle(color: Colors.white),
      threadUnreadMessageCountBackgroundColor: Colors.red,
    ),
  ),
  child: MyApp(),
)
PropertyTypeDefault
paddingEdgeInsetsGeometry?EdgeInsets.symmetric(vertical: 14, horizontal: 8)
backgroundColorColor?StreamColorScheme.backgroundApp
threadChannelNameStyleTextStyle?captionEmphasis with textTertiary color
threadReplyToMessageStyleTextStyle?bodyDefault
threadLatestReplyMessageStyleTextStyle?bodyDefault
threadReplyCountStyleTextStyle?captionEmphasis with textLink color
threadLatestReplyUsernameStyleTextStyle?captionEmphasis with textPrimary color
threadLatestReplyTimestampStyleTextStyle?captionDefault with textTertiary color
threadLatestReplyTimestampFormatterDateFormatter?formatRecentDateTime (outputs "Just now", "Today at 9:41", "Yesterday at 9:41", etc.)
threadUnreadMessageCountStyleTextStyle?numericXl with textOnAccent color
threadUnreadMessageCountBackgroundColorColor?accentPrimary

Sub-widgets

StreamThreadListTile is composed of several smaller widgets that can also be used independently:

WidgetDescription
ThreadTitleDisplays the channel name as a single-line label
ThreadRootMessagePreviewShows the parent message as a formatted preview using StreamMessagePreviewText
ThreadFooterDisplays participant avatars (StreamUserAvatarStack), reply count, and a relative timestamp
ThreadUnreadCountUnread message count badge

Other Customizations

The StreamThreadListView widget provides a few other parameters that can be used to customize the appearance and behavior of the widget:

StreamThreadListView(
  controller: controller,
  emptyBuilder: (context) {
    // Return your custom empty state widget here.
  },
  loadingBuilder: (context) {
    // Return your custom loading state widget here.
  },
  errorBuilder: (context, error) {
    // Return your custom error state widget here.
  },
  separatorBuilder: (context, threads, index) {
    // Return your custom separator widget here.
  },
);