Info:
This is documentation for Stream Chat Flutter SDK v9, which is no longer actively maintained. For up-to-date documentation, see the latest version (v10).

Livestream Best Practices

This guide covers Flutter-specific practices for building high-traffic livestream and live event chat using the Stream Chat Flutter SDK. It supplements the general livestream best practices with guidance specific to the Flutter rendering pipeline and the SDK's Flutter APIs.

For UI layout patterns (split-screen, overlay), see Adding Chat to Video Livestreams.

Disable Offline Storage

The Flutter SDK supports offline persistence via the stream_chat_persistence package, which uses Drift (SQLite) under the hood. For livestream use cases, offline storage should be disabled. It introduces write overhead on every incoming message, which becomes a bottleneck under high message volume.

How to Disable

Do not assign a chatPersistenceClient to your StreamChatClient. The default behavior (no persistence) is correct for livestreams:

final client = StreamChatClient(
  'your-api-key',
  logLevel: Level.INFO,
);

If you are migrating from a standard chat implementation that uses persistence, remove the persistence setup:

// Remove this for livestream channels
final chatPersistentClient = StreamChatPersistenceClient(
  logLevel: Level.INFO,
  connectionMode: ConnectionMode.background,
);
client.chatPersistenceClient = chatPersistentClient;

Why This Matters

Every message.new event triggers a write to the local SQLite database when persistence is enabled. At 5-10+ messages per second (peak livestream traffic), this means:

  • Continuous disk I/O on every incoming message, even with Drift's background isolate
  • Memory pressure from maintaining a synchronized local cache that has no value in a livestream context (users don't scroll back through thousands of ephemeral messages offline)
  • Additional CPU cycles for serialization/deserialization to and from the database

In a livestream, the message list is ephemeral and forward-only. Offline storage adds cost with no user benefit.

Cap the Loaded Message List

ChannelClientState keeps every message it has loaded for as long as the channel stays open. StreamMessageListView accepts a maximumMessageLimit that caps the loaded list and drops the oldest messages as new ones arrive.

The cap is off by default (null), so nothing is trimmed unless you ask for it. Set it explicitly on any livestream channel.

How to Enable

Pass the limit to the message list:

StreamMessageListView(
  maximumMessageLimit: 300,
);

A fixed 30-message buffer is allowed above the cap before a trim runs, so the list settles between 300 and 330 messages instead of trimming on every incoming message.

When Trimming Runs

Trimming is deliberately conservative. It only fires when all of these hold:

  • The channel is up to date, meaning the user is at the live edge rather than scrolled back into history
  • A new message arrived, or the user paginated towards the bottom
  • The loaded count exceeds maximumMessageLimit plus the 30-message trim buffer

Top pagination, edits, reactions, deletions, jump-to-message, and thread views never trigger a trim.

Only the client-side state is affected. Nothing is deleted on the server, and the top-pagination marker is reset whenever a trim happens, so scrolling back up re-fetches the older messages from the API.

Why This Matters

In a regular chat, the loaded list is bounded by the size of the conversation. In a livestream it is bounded by the length of the stream. At 5-10 messages per second, a one-hour event leaves tens of thousands of Message objects in the channel state, and none of that memory is reclaimed while the channel stays open. That is the steady growth pattern described under Memory Profiling below.

Viewers of a live event follow the newest messages and rarely scroll far back, so almost none of that history is ever read. A cap of 200-500 messages is more scrollback than they realistically use, and it keeps memory flat however long the stream runs.

Testing Render Performance with Flutter DevTools

High message throughput exposes rendering issues that don't surface in normal development. The Flutter DevTools is a great tool to to identify and fix them.

Overview of the Flutter DevTools

Opening DevTools

flutter run --profile # Note: Use a physical device when profiling
# Then press 'v' to open DevTools in your browser

Always profile in profile mode (--profile), not debug mode. Debug mode disables optimizations and gives misleading performance data.

Frame Rendering (Performance Overlay)

Enable the performance overlay in DevTools or programmatically:

MaterialApp(
  showPerformanceOverlay: true,
);

Watch for:

  • Jank frames (>16ms build/render): any frame that exceeds 16ms will cause visible stutter
  • Consistent green bars: this is the target; sustained green means you're hitting 60fps
  • Red/yellow spikes correlated with incoming messages: indicates expensive rebuilds triggered by new messages

Widget Rebuild Tracking

In the DevTools Widget Inspector, enable "Track Widget Rebuilds" to see which widgets rebuild on each incoming message. Common culprits in livestream chat:

  • The entire message list rebuilding instead of just inserting a new item
  • User avatar widgets re-fetching or re-decoding images on every rebuild
  • Rich text/link preview widgets re-parsing content unnecessarily

Ensure Widgets Have Keys

In a high-velocity list, Flutter needs stable keys to efficiently diff the widget tree. Without keys, the framework may rebuild entire list sections instead of inserting a single new item.

ListView.builder(
  itemCount: messages.length,
  itemBuilder: (context, index) {
    final message = messages[index];
    return MessageWidget(
      key: ValueKey(message.id),
      message: message,
    );
  },
);

The Stream Flutter SDK's built-in StreamMessageListView already handles keys correctly. If you're building a custom message list, this is critical.

Memory Profiling

Open the Memory tab in DevTools while BenChat is sending traffic. Watch for:

  • Steady memory growth: indicates a leak, commonly from event listeners not being disposed, or an unbounded message list that never trims old messages
  • GC pressure (frequent garbage collection pauses): often caused by creating many short-lived objects per message (e.g., rebuilding TextStyle, BoxDecoration objects on every frame)

Cap your visible message list with maximumMessageLimit. Keeping 200-500 messages in memory is sufficient.

Timeline View

The Timeline tab shows exactly what happens on the UI thread and raster thread per frame. Filter for frames that exceed the 16ms budget and look for:

  • Long build() calls: your widget tree is too expensive to construct
  • Expensive paint() operations: shadows, clip paths, or complex custom painters
  • Layout thrashing: widgets that trigger repeated layout passes

Common Flutter Performance Traps

Issue Symptom Fix
Missing keys on list items Entire list rebuilds; scroll position jumps Add ValueKey(message.id) to each item
Unbounded message list Memory grows indefinitely Set maximumMessageLimit to 200-500 on the message list
Avatar images re-fetched per rebuild Network spikes, image flicker Use CachedNetworkImage or precache
Heavy build() in message widget Jank spikes on every new message Extract static parts into const widgets; use RepaintBoundary
Opacity/ClipRRect on every message Raster thread spikes Minimize per-item clip operations; use saveLayer sparingly
State management rebuilds too broadly Entire screen rebuilds on message events Scope state listeners to the message list widget only
Animations on every message insert Compounding animation overhead at high frequency Disable insert animations in livestream mode or throttle them

Quick Checklist

Before going live with your Flutter livestream chat:

  • Using the livestream channel type (or a custom type with equivalent settings)
  • Read events, typing indicators, and connect events are disabled
  • Offline storage is not attached to the StreamChatClient
  • Slow mode is configured for expected traffic levels
  • BenChat stress test has been run at 5-10 msg/sec
  • Flutter DevTools profiling completed in profile mode with no jank
  • maximumMessageLimit is set on the message list (200-500)
  • All list items have stable ValueKeys
  • Avatar/image loading uses caching (CachedNetworkImage or similar)
  • Auth and permission checks are enabled in production
  • Moderation tools (block lists, automod, flagging) are configured