Skip to content

Message Read Indicators

Read indicators show the sender when their messages have been seen by other channel members. This page covers message delivery states — the per-message sending status icon shown in the footer of the sender's messages — and how to access the underlying read and delivery state programmatically.

Message delivery states

StreamMessageSendingStatus is rendered in the footer of every outgoing message (messages sent by the current user). It displays one of four states:

State Icon When
Sending Clock Message is in flight (or attachments are still uploading)
Sent Single checkmark Message reached the server but has not been delivered to any peer
Delivered Double checkmark (grey) Message delivered to at least one other member's device
Read Double checkmark (accent) At least one other member has read up to this message

No configuration is required — the status updates automatically as events arrive from the Stream backend. The widget is only shown for the current user's own messages; incoming messages from others never display a delivery status.

If attachments are still uploading, a progress text (e.g. "Uploading 1/3…") is shown in place of the checkmark icon until the upload completes.

Enabling or disabling read events

Read events must be enabled at the channel level for read indicators to function. They are enabled by default on most channel types. Confirm this in your Stream Dashboard under channel type settings.

Marking messages as read

StreamMessageListView automatically marks the channel as read once the user scrolls to the bottom of the list (controlled by markReadWhenAtTheBottom, default true). To mark messages as read manually (for example, when the app returns to the foreground), call:

await channel.markRead();

Reading the channel read and delivery state

The full read and delivery state for all channel members is available on channel.state?.read, which returns a List<Read>. Each Read object covers both concepts:

Field Description
user The channel member this entry belongs to
lastRead Timestamp of the most recent read event for this member
lastReadMessageId ID of the last message this member has read
lastDeliveredAt Timestamp of the most recent delivery confirmation (may be null if delivery events are not enabled)
lastDeliveredMessageId ID of the last message delivered to this member's device
unreadMessages Number of unread messages for this member
final channel = StreamChannel.of(context).channel;
final reads = channel.state?.read; // List<Read>

To query the state for a specific message, use the extension methods on List<Read>:

// Members who have read up to (and including) a given message
final readers = reads?.readsOf(message: message) ?? [];

// Members who have received (delivered) a given message but may not have read it yet
final delivered = reads?.deliveriesOf(message: message) ?? [];

readsOf returns members whose lastRead is on or after the message's createdAt (excluding the sender). deliveriesOf returns members whose lastDeliveredAt is on or after the message's createdAt, or who have already read it.

To react to changes in real time, subscribe to the stream instead:

channel.state?.readStream.listen((reads) {
  // called whenever any member's read or delivery state changes
});