In Flutter, which ConnectionState values can an AsyncSnapshot pass through for a FutureBuilder versus a StreamBuilder, and what does initialData change?
answer
- none, waiting, active, done
- null source means none
- active is the stream's state
- error on a stream stays active
- initialData seeds data, not state
basics
~20 sConnectionState is none, waiting, active or done. A FutureBuilder goes none or waiting to done; a StreamBuilder goes waiting, then active per event, then done on close. initialData fills snapshot.data before the first result, without changing the state.
solid answer
~40 s`ConnectionState` has four values. `none` means no source: the `future` or `stream` is null. `waiting` means subscribed but nothing received yet. `active` is used only by `StreamBuilder`, once events arrive and until the stream closes. `done` means the future completed or the stream closed. A `FutureBuilder` snapshot therefore moves `waiting` to `done` (the builder may skip `waiting`), while a `StreamBuilder` moves `waiting` to `active` for each event and to `done` on close. A stream error produces an `active` snapshot with the error and no data; a later value replaces it. `initialData` makes `data` non-null from the first build, so `hasData` is true while still `waiting`: code that branches on `hasData` alone never shows a spinner.
code
dart · 17 linesStreamBuilder<double>(
stream: temperatureStream,
initialData: 18.0,
builder: (context, snapshot) {
final state = snapshot.connectionState;
if (snapshot.hasError) {
return Text('Sensor error: ${snapshot.error}');
}
return Row(
children: [
Text('${snapshot.requireData.toStringAsFixed(1)} C'),
if (state == ConnectionState.waiting) const Text(' (connecting)'),
if (state == ConnectionState.done) const Text(' (sensor closed)'),
],
);
},
)go deeper
Recall the four ConnectionState names and that none means the future or stream is null.
Explain the future and stream sequences, that active belongs to streams, and how initialData makes hasData true while still waiting.
Use connectionState and data together to show stale-while-refreshing UI, and handle stream errors as recoverable active snapshots rather than terminal failures.
Set a team convention for async screen states so loading, stale, error and ended look consistent across features, whatever widget or library produces them.
## The four states `ConnectionState` is an enum in the widgets library describing how a builder is connected to its asynchronous source: | Value | Meaning | Typical data | |---|---|---| | `none` | not connected: the source is `null` | `initialData`, or previous result | | `waiting` | subscribed, nothing received yet | `initialData`, or previous result | | `active` | a stream has produced events and is still open | latest event | | `done` | the future completed, or the stream closed | final value, or error | `connectionState` answers *where in its lifecycle* the source is; `data` and `error` answer *what it last produced*. They are independent, which is why checking one without the other misleads. ## FutureBuilder's sequence A `FutureBuilder` never uses `active`: 1. On `initState`, the snapshot starts as `none` (with `initialData` if given). 2. If `future` is non-null, it subscribes and moves to `waiting`. 3. When the future completes, `setState` sets `done` with data, or `done` with an error. The builder is called when the pipeline rebuilds, so it may see both snapshots or only `done`. A new future that has **already completed** still produces one frame of `waiting`, because Dart has no way to tell synchronously that a `Future` is complete. Switching to a new future keeps the old data: the snapshot passes through `none` and `waiting` still carrying the previous result. ## StreamBuilder's sequence 1. Starts at `none`, or `none` with `initialData`. 2. After `listen`, moves to `waiting`, keeping any `initialData`. 3. Each data event produces `active` with that value. 4. Each **error event** produces `active` with the error and **no data**. The stream keeps going; the next data event replaces the error-only snapshot. 5. When the stream closes, the snapshot moves to `done`, keeping its last data or error. If you give it a different stream, it cancels the old subscription, moves to `none` with the old data, then `waiting` on the new stream. ## What initialData changes **`initialData`** seeds `snapshot.data` so the first frames have something to show: a cached forecast, a zero count, an empty list. It changes **data, not state**: - `hasData` is `true` from the very first build, while `connectionState` is still `waiting`. - A builder that branches only on `hasData` therefore never shows a loading indicator. - For a `FutureBuilder`, if the future fails, `data` becomes `null` regardless of `initialData`, and the error is in `snapshot.error`. That is often exactly what you want (show cached data immediately and replace it quietly), but if the design needs a "refreshing" hint, combine the checks: data present *and* `connectionState == ConnectionState.waiting`. ## Branching correctly - Use `hasError` and `hasData` to decide **what content** to show. - Use `connectionState` to decide **whether work is in flight** (a small progress bar over stale data) or whether a stream has **ended** (`done`). - Treat `ConnectionState.none` as a configuration state: the source is null, for example before the user has picked a city. - A `Future<void>` completes with `null`, so detect its success with `done` and no error. ## Summary for an interview - `active` belongs to streams; futures go straight from `waiting` to `done`. - Stream errors are `active` snapshots without data, not a terminal state. - `initialData` makes `hasData` true during `waiting`. - Snapshots may be skipped; never count on seeing every one.
- In Flutter, does a StreamBuilder stop listening after the stream emits an error?No. The error event produces an `active` snapshot with the error and no data, and the subscription stays open. If the stream emits another value, the next snapshot is `active` with that value and no error. Only closing the stream moves it to `done`.
- In Flutter, why can a FutureBuilder given an already-completed future still show one frame of waiting?`FutureBuilder` subscribes with `then`, which reports the result asynchronously for ordinary futures, and there is no synchronous way to ask a `Future` whether it has completed. So the first build after subscribing sees `waiting`, and the result arrives on the next rebuild.
saying these in an interview costs you the question
- FutureBuilder reports ConnectionState.active while the request runs
- A stream error moves StreamBuilder to done and ends the subscription
- initialData changes connectionState to done on the first build
- ConnectionState.none means the request is still loading
- Every snapshot state is guaranteed to reach the builder