Skip to content

FlowThread renders a conversation from plain message data — user, assistant, and system roles — and FlowMessage renders a single turn: an ink-wash bubble for the user, plain text on the page for the assistant. Neither knows where the messages came from.

A three-role conversation
FlowThread(
messages: [
FlowMessageData.text(
id: '1',
role: FlowMessageRole.user,
text: 'What is flow_ui?',
),
FlowMessageData.text(
id: '2',
role: FlowMessageRole.assistant,
text: 'An open-source Flutter UI library to build production-grade Chat & AI assistant interfaces...',
),
FlowMessageData.text(
id: '3',
role: FlowMessageRole.system,
text: 'Model changed to Sonnet',
),
],
)

Streaming is data, not streams: as chunks arrive, rebuild with the updated message list and the thread animates the reveal. Status walks pending → streaming → complete.

Stream by rebuilding
FlowThread(
messages: [
...history,
FlowMessageData(
id: 'reply',
role: FlowMessageRole.assistant,
parts: [FlowTextPart(streamedSoFar)],
status: FlowMessageStatus.streaming,
),
],
)

A pending assistant message shows the thinking indicator until the first token arrives; an error status wraps the content in an error bubble.

Pending and error states
// Pending: the thinking indicator until the first token arrives.
FlowMessage(
FlowMessageData(
id: 'p',
role: FlowMessageRole.assistant,
status: FlowMessageStatus.pending,
),
thinkingLabel: 'Thinking…',
)
// Error: content in an error bubble.
FlowMessage(
FlowMessageData.text(
id: 'e',
role: FlowMessageRole.assistant,
text: 'Something went wrong while generating a response.',
status: FlowMessageStatus.error,
),
)

Message content is typed parts, not strings: a sealed FlowMessagePart with FlowTextPart, FlowAttachmentPart, and FlowCustomPart subtypes. FlowCustomPart plus a FlowCustomPartBuilder on the message is the extension seam — it carries a type your builder switches on and an arbitrary data payload, so the host injects its own content (a tool card, a chart) without the library knowing what it is.

  • FlowThread — the scrolling conversation; give it a ScrollController to pair with FlowChatScreen’s jump-to-latest. padding (the design’s 16) and itemSpacing (32) override the metrics; messageBuilder swaps the default FlowMessage per turn; thinkingLabel, charactersPerSecond, and previewCloseTooltip forward to every message.
  • FlowMessage — one turn; accepts a FlowCustomPartBuilder, an onAttachmentTap override, leading and footer slots (an avatar, the action row), a textStyle, and the user bubble’s overrides — maxBubbleWidthFraction (0.75), bubbleRadius, bubblePadding.
  • FlowMessageDataid, role, parts, status, and an optional timestamp; the .text constructor covers the plain case.