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:

StateIconWhen
SendingClockMessage is in flight (or attachments are still uploading)
SentSingle checkmarkMessage reached the server but has not been delivered to any peer
DeliveredDouble checkmark (grey)Message delivered to at least one other member's device
ReadDouble 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:

FieldDescription
userThe channel member this entry belongs to
lastReadTimestamp of the most recent read event for this member
lastReadMessageIdID of the last message this member has read
lastDeliveredAtTimestamp of the most recent delivery confirmation (may be null if delivery events are not enabled)
lastDeliveredMessageIdID of the last message delivered to this member's device
unreadMessagesNumber 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
});