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
maximumMessageLimitplus 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.

Opening DevTools
flutter run --profile # Note: Use a physical device when profiling
# Then press 'v' to open DevTools in your browserAlways 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,BoxDecorationobjects 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
livestreamchannel 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
-
maximumMessageLimitis set on the message list (200-500) - All list items have stable
ValueKeys - Avatar/image loading uses caching (
CachedNetworkImageor similar) - Auth and permission checks are enabled in production
- Moderation tools (block lists, automod, flagging) are configured