---
name: stream-flutter
description: "Build and integrate Stream Chat, Video, and Feeds in Flutter apps. Use for Flutter/Dart project work with Stream package setup, auth wiring, and widget blueprints. Supports stream_chat_flutter (pre-built Chat UI), stream_chat_flutter_core (custom Chat UI), stream_video_flutter (Video calling and livestreaming), and stream_feed / stream_feed_flutter_core (Activity Feeds, no pre-built UI). Also migrates Flutter apps from the Sendbird Chat SDK (sendbird_chat_sdk / sendbird_uikit) to Stream Chat."
license: See LICENSE in repository root
compatibility: Works in an existing Flutter project, or scaffolds a new one. No Stream CLI required.
metadata:
  author: GetStream
allowed-tools: >-
  Read, Write, Edit, Glob, Grep,
  Bash(ls *),
  Bash(grep *),
  Bash(find . *),
  Bash(cat pubspec.yaml), Bash(cat pubspec.lock),
  Bash(flutter pub *),
  Bash(flutter create *),
  Bash(flutter run *),
  Bash(flutter analyze *), Bash(flutter analyze),
  Bash(flutter build *),
  Bash(flutter devices),
  Bash(flutter emulators *), Bash(flutter emulators),
  Bash(getstream token *),
  Bash(getstream env *),
  Bash(getstream api *),
  Bash(getstream login *),
  Bash(getstream init *)
---

# Stream Flutter - skill router + execution flow

**Rules:** Read **[`RULES.md`](RULES.md)** once per session - every non-negotiable rule is stated there, nowhere else.

This file is the **single entrypoint**: intent classification, local project detection, and module pointers for Stream work in Flutter apps.

---

## Step 0: Intent classifier (mandatory first - never skip)

Before any tool call, decide the **track** from the user's input alone - no probes first.

### Signals -> track

| Signal in user input                                                                                                                                          | Track                                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Explicit package/widget token: `stream_chat_flutter`, `StreamChannelListView`, `StreamMessageListView`, `StreamChatClient`, etc.                              | **C - Reference lookup**                                                                                                                            |
| Explicit video token: `stream_video_flutter`, `StreamCallContainer`, `StreamVideo`, `StreamVideoRenderer`, `goLive`, `stopLive`, `livestream` call type       | **C - Reference lookup**                                                                                                                            |
| Explicit feeds token: `stream_feed`, `stream_feed_flutter_core`, `StreamFeedClient`, `FlatFeedCore`, `FlatFeed`, `FeedBloc`, `activity feed`, `feeds flutter` | **C - Reference lookup**                                                                                                                            |
| Words "docs" or "documentation" around Stream Flutter work                                                                                                    | **C - Reference lookup**                                                                                                                            |
| "How do I {X} in Flutter?", "What does {widget/method} do?"                                                                                                   | **C - Reference lookup**                                                                                                                            |
| "Build me a new Flutter app", "create a Flutter chat app" + Stream                                                                                            | **A - New app**                                                                                                                                     |
| "Build a Flutter video call app", "create a livestream app in Flutter"                                                                                        | **A - New app** (load `VIDEO-FLUTTER.md` + `VIDEO-FLUTTER-blueprints.md` or `LIVESTREAM-FLUTTER.md` + `LIVESTREAM-FLUTTER-blueprints.md`)           |
| "Build an audio room / Twitter Spaces clone", "TikTok-style live feed", "call while livestreaming", "chat with video calls", "two calls at once"              | **A or B** (load `VIDEO-ADVANCED-FLUTTER.md` + `VIDEO-ADVANCED-FLUTTER-blueprints.md` on top of the Video/Livestream pair)                          |
| "Add ringing / incoming calls", "video call with push notifications", "CallKit", "VoIP push", "FCM ringing", "missed call notification"                       | **A or B** (load `RINGING-FLUTTER.md` + `RINGING-FLUTTER-blueprints.md` on top of the Video pair)                                                   |
| "Build a Flutter feeds app", "create an activity feed app", "build a social feed in Flutter", "create a Twitter/Instagram clone"                              | **A - New app** (load `FEEDS-FLUTTER.md` + `FEEDS-FLUTTER-blueprints.md`; use Twitter-style UI unless the user explicitly specifies otherwise)      |
| "Add/integrate Stream into this app", "wire Chat into my Flutter project"                                                                                     | **B - Existing app**                                                                                                                                |
| "Add video calling to my Flutter app", "integrate Stream Video into my existing app"                                                                          | **B - Existing app** (load `VIDEO-FLUTTER.md` + `VIDEO-FLUTTER-blueprints.md`)                                                                      |
| "Add a feed to my Flutter app", "integrate Stream Feeds into my existing app", "add activity feed"                                                            | **B - Existing app** (load `FEEDS-FLUTTER.md` + `FEEDS-FLUTTER-blueprints.md`; use Twitter-style UI unless the user explicitly specifies otherwise) |
| "Install Stream packages", "set up Stream in Flutter", "wire auth/token" with no broader feature request                                                      | **D - Bootstrap / setup**                                                                                                                           |
| "Migrate my Flutter app from Sendbird", "replace `sendbird_chat_sdk` / `sendbird_uikit` with Stream", "switch off Sendbird / SendbirdUIKit"                    | **M - Migrate from Sendbird** ([`sendbird-migration.md`](sendbird-migration.md))                                                                    |
| Bare `/stream-flutter` with no args                                                                                                                           | List the tracks briefly and wait                                                                                                                    |

### Styling-depth flag (orthogonal to Tracks A/B/C/D)

If the request carries a **target appearance** — an attached screenshot, a Figma frame, or "make it look like WhatsApp / iMessage / Telegram / Slack / <app>" — then **before** feature work (Track A or B), **first run the strategy decision below** (components vs custom):

- **Components** → run [`design-matching.md`](design-matching.md): read its **procedure half in full** — everything above the `# Reference` divider (Steps 1–5; it may span two Reads, so page past a mid-procedure truncation) — then Grep its Reference half per region while implementing.
- **Custom** (livestream / overlay / bespoke) → run [`custom-ui.md`](custom-ui.md) instead — same region-by-region rigor, different mechanism.

The rest of this section is the **components** case (the common one). A reference design is a checklist of regions (header, composer buttons, where the timestamp + read receipts sit, bubble shape/tail, date separators, attachments...), and most of them differ from Stream's defaults **structurally**, not just by color. Do **not** stop at the wallpaper and bubble color — that is the known failure mode. Decompose every region first (capturing its **dimensions**, not just colors), then route each to one of **two axes**:

1. **Theming** — `StreamTheme` (a `ThemeExtension` on `MaterialApp.theme.extensions`) owns design foundations (spacing / radius / typography / colors) **and** all fine-grained/leaf styling including the whole message row (`messageItemTheme` bubble/text/attachment/metadata, `reactionsTheme`, `reactionPickerTheme`, `avatarTheme`, `textInputTheme`, `mediaViewerTheme`, …), while `StreamChatThemeData` (passed to `StreamChat(themeData:)`) owns only the chat composite-widget slots (the three headers, message-list background/wallpaper, channel-list item, quoted message, thread tile, poll themes).
2. **Widget replacement** — the component factory, populated via **core named slots** on `StreamComponentBuilders(...)` (~48 leaf slots: `messageBubble`, `messageText`, `reactions`, `reactionPicker`, `mediaViewer`, `jumpToUnreadButton`, `textInput`, `avatar`, …) **and** **chat slots** via `streamChatComponentBuilders(...) → extensions:` (~30 composite slots: `messageItem`, `messageComposer` + sub-slots, `channelListItem`, per-attachment builders, …), passed to `StreamChat(componentBuilders: StreamComponentBuilders(...))` (global) or `StreamComponentFactory(builders: …, child: …)` (scoped); plus per-widget builders (`messageBuilder`, `itemBuilder`) for a single instance.

Recurring traps the doc guards against:

- **(a)** overriding a **composite widget** (`messageItem` builder, `messageComposer` builder, `messageBuilder` on `StreamMessageListView`) silently drops the sub-features the default rendered — the incoming-message avatar, grouping, reactions, replies, status, or the send/voice/edit/slow-mode button — unless you read the default's `build()` and reproduce them (grouping state is available inside a custom `messageItem` via `StreamMessageLayout.of(context)`);
- **(b)** **model-driven title logic** must live in one shared helper used by both the channel list `itemBuilder` and the header, so the two surfaces cannot diverge;
- **(c)** route deliberately — bubble padding / color / shape live on **`StreamTheme`** (`StreamTheme.messageItemTheme.bubble`); structural changes (send button outside the field, metadata inside the bubble, a bubble tail, a Slack-style flat row) need **widget replacement** (component factory or a per-widget builder).

The match is **not done until you run, seed data that triggers every region, compare region-by-region against the reference on the real navigation path, and iterate** ([`design-matching.md`](design-matching.md) Step 5), deleting any throwaway verification scaffold — the UI must be as close to the reference as possible, not approximately like it. **Implement every region, the composer included** — never deliver a partial match with the rest labelled "known cosmetic gaps"; a region left at the SDK default is a FAIL, not a footnote. And **work in batches**: ground the pinned SDK version + local checkout once, read the source you need in one pass, implement all regions, then verify once on hot reload — don't rebuild-and-screenshot after every small edit.

### Chat only: pick the UI strategy first (before any code)

Stream Chat ships **two layers**, and choosing between them is an **architecture decision that dwarfs any styling choice** — getting it wrong wastes a day either way. So decide deliberately, and **default to the pre-built components**:

| Strategy | Use when the design is... | Mechanism | Runbook |
|---|---|---|---|
| **Pre-built UI components** (`stream_chat_flutter`) — **the default** | A messenger: bubbles, or a channel list → conversation, or per-message avatar/timestamp/receipts/reactions/attachments. Social, marketplace, workplace, support, DMs. "Make it look like WhatsApp / iMessage / Telegram / Slack." | Customize via the two axes — theming (`StreamTheme` for foundations + the message row/leaf widgets, `StreamChatThemeData` for the chat composite widgets) + widget replacement (the component factory `StreamComponentBuilders` / `streamChatComponentBuilders` / `StreamComponentFactory`, plus per-widget builders) | [`design-matching.md`](design-matching.md) |
| **Custom UI on `stream_chat_flutter_core`** — the exception | Not a messenger: a flat bubble-less author-inline feed, an overlay/ticker on video, high-volume ephemeral livestream chat (Twitch / YouTube / Kalshi), live shopping, or anonymous/guest read-only viewers vastly outnumbering posters (→ `livestream` channel type). Every message rendered identically; bespoke app chrome around it. | Build your own widgets on the headless `stream_chat_flutter_core` controllers (+ the low-level client); **no** `stream_chat_flutter` widgets | [`custom-ui.md`](custom-ui.md) |

**Lean hard toward components.** They're built to be *customized*, and the litmus test is: *if the two axes — theming (`StreamTheme` + `StreamChatThemeData`) + a few component-builder slots — could get there, it's a components job* — even strong messenger reskins. Pick **custom only** when matching the design would mean replacing the message row, composer, header, AND list all at once — i.e. you'd be using the SDK purely as a data source, not for any of its widgets. Over-choosing custom (rebuilding a worse messenger by hand, losing avatars/grouping/reactions/threads/attachments/typing/receipts/pickers) is the common, expensive mistake; over-choosing components costs a few hours of fighting layout. **When unsure, build the components version first** — it's faster to confirm-or-reject. And treat **"livestream" / "live-shopping" as signals, not labels**: they point to custom only when the *shape* matches (flat identical rows, overlay/ticker, high volume, read-only viewers) — a livestream app that wants a normal bubble/channel-list chat panel stays on **components**. This is the full decision rubric; [`custom-ui.md`](custom-ui.md) is the build runbook you follow *after* the decision lands on custom, not a doc you open to decide.

**Workplace / Slack-style hybrid is a components job too.** A Slack/Teams/Discord surface is a channel list + message list + composer, so it stays on the pre-built components — but two things differ from a messenger and must be matched, not punted: (1) the **message row** is flat and left-aligned (avatar-top rounded-square, an author·custom-status·timestamp header line, body, **bottom reaction pills**, and a **thread-reply summary**), with **no incoming/outgoing bubble split** — reproduce it by overriding the `messageItem` component builder (a composite slot — reproduce its sub-features); (2) the **header** and chrome are custom. The full workplace archetype is in [`design-matching.md`](design-matching.md). Workplace apps are also **thread-first**, so wire the thread-reply summary and thread screen.

The strategy also picks the **channel type and permission model** (e.g. `messaging` membership-gated for social/marketplace vs `livestream` public + anonymous viewers) — see [`RULES.md`](RULES.md) → "Surface permission prerequisites proactively", and Step 0.5 → "Permissions awareness". Decide both axes together.

If it's genuinely unclear, ask one question:

> Does this chat look like a standard messenger (channel list + bubbles), or a bespoke surface like livestream/overlay chat? It decides whether we customize the pre-built components or build custom UI on `stream_chat_flutter_core`.

### Disambiguation flow

If the request is ambiguous between **build/integrate** and **reference lookup**, ask one short question and wait:

> Do you want me to wire this into the project, or just map the Flutter SDK pattern and widgets?

### After classification

- **Tracks A, B, D** -> run **Project signals** once per session, then continue in [`builder.md`](builder.md) and [`sdk.md`](sdk.md). If the styling-depth flag was raised, run the design-match phase alongside per the UI-strategy decision — [`design-matching.md`](design-matching.md) for the pre-built components (the common case; read its procedure half in full — everything above the `# Reference` divider, which may span two Reads — then Grep its Reference half per region) or [`custom-ui.md`](custom-ui.md) for a bespoke `stream_chat_flutter_core` surface (design-match rigor is a mandatory phase, not an optional add-on).
- **Track C** -> skip the probe if the product + package are explicit. Only run it on demand if the SDK layer is ambiguous.
- **Track M** -> run [`sendbird-migration.md`](sendbird-migration.md): detect the existing Sendbird integration shape, swap packages/init/views/channels/messages, re-apply theming via the two axes, and verify design + functional fidelity per screen against the Sendbird original. Reuses Step 0.5 credentials and [`design-matching.md`](design-matching.md)/[`custom-ui.md`](custom-ui.md) rigor; hands off to the shared data-migration runbook when the code migration is done.

---

## Step 0.5: Credentials, token, and seed data (tracks A, B, D — and Track M at phase M2)

Run this once per session, right after intent classification, before the Project signals probe.

### Goal

Collect the Stream **API key**, a **user token**, and optionally seed channels or calls - all before touching code - so the app has real data to show from the first run.

### Single upfront question (ask exactly once, then act immediately)

Post **one message** asking all relevant things together. Do not split into multiple rounds.

**For Chat projects:**

> To wire everything up with real data, I need a few quick answers:
>
> 1. **Credentials** - Should I fetch your API key from the dashboard and generate a token via the Stream CLI, or will you paste them yourself?
> 2. **Token expiry** - If I'm generating the token: should it expire? (e.g. `1h`, `1d`, `30m`) or never expire?
> 3. **Seed channels** - Should I pre-create a few channels with random usernames so the app has something to show immediately?
>
> If you want to handle everything yourself, just paste your API key and token and tell me whether to seed channels.

**For Video projects** (calls are ephemeral - no seeding needed):

> To wire everything up, I need a couple of quick answers:
>
> 1. **Credentials** - Should I fetch your API key from the dashboard and generate a token via the Stream CLI, or will you paste them yourself?
> 2. **Token expiry** - If I'm generating the token: should it expire? (e.g. `1h`, `1d`, `30m`) or never expire?
>
> If you want to handle everything yourself, just paste your API key and token.

> **Guest-viewer requirement — surface this BEFORE building any app that signs viewers in as guests**. A guest connects with the `guest` role, which by default has minimal capabilities. On the `livestream` call type guests **cannot even read or join a call** until the integrator grants those capabilities to the `guest` role. When a guest-based viewer flow is in scope, tell the integrator it is a **prerequisite** to grant the `guest` role `read-call` and `join-call` (plus `create-call` if a viewer may open the call before the host) on the relevant call type via Stream Dashboard → Video & Audio → Call Types → <type> → Roles & Permissions (or the API), or to use authenticated `User.regular` viewers instead. Details: [`references/VIDEO-FLUTTER.md`](references/VIDEO-FLUTTER.md) → Guest users, and [`references/LIVESTREAM-FLUTTER.md`](references/LIVESTREAM-FLUTTER.md) → Roles, permissions, and backstage security.

**For Feeds projects** (no pre-built UI; feed groups required):

Ask **one message** with