Build1 distinct publisher3 min readPublished
The package author argues Flutter teams reach for FutureBuilder out of friction rather than ignorance, so 1.1.0 answers with one method call and a required initialState that turns frame zero into somebody's explicit decision.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
Call `api.fetchUserProfile(userId).toBlocSignal(initialState: User.anonymous())` and the container holds `User.anonymous()` on frame 0, then emits the resolved `User` synchronously into the state machine when the future completes [6]. A throw routes straight to `onError()` and to the container's registered `BlocObserver`, rather than sitting in a snapshot field waiting for you to test it [7].
The required parameter is the part of this design worth defending. A `ReadonlySignal` converts with no arguments at all, because the `signals_core` graph always holds an immediate synchronous value [4]. A `Future` has no such value on frame 0, so the raw-value track will not give you a container without `initialState:` [5]. That is the frame-0 problem lifted out of a builder callback and written into a signature, where the compiler asks the question instead of the reviewer.
Count the surface before planning a migration. Four adaptable source kinds against two tracks gives eight call shapes to learn [13], set against the six setup steps the post enumerates for the traditional route [12]. Lifted primitives are in scope too: `49.99.$.toBlocSignal()` yields a `BlocSignalBase<double>`, and a `computed` predicate over a username and password length yields a `BlocSignalBase<bool>` [18].
The adapter decides how a source becomes a container, but where you call it is still your call. The post's own history is the evidence: the official Flutter video on `FutureBuilder` instantiated the network future directly inside `build()` until the author filed an issue to correct it, which is why the clapperboard in that video still says "Take 2" [14]. The post's position is that these builders are architectural anti-patterns specifically when placed inside widget trees [15]. An adapter is an expression like any other, and the container it returns carries a `close()` that unsubscribes the effect [8], so the call site and the disposal owner remain a human decision.
"Friction disappears completely" is a claim about the author's own workload [16], and the author's stated diagnosis is that developers never wanted the bad architecture in the first place [10]. For the claim to transfer, a few things have to hold in your codebase. You have to be willing to take the signals graph into your dependency tree at all. Your async source's lifetime has to line up with a container that something will close. And your frame 0 needs an honest domain default, because `User.anonymous()` is a legitimate state in some products and a latent authorisation bug in others.
Architectural advice about `FutureBuilder` has been available for years, including a video from this same author titled around not putting it in your build method [14], and the pattern kept shipping anyway. A required constructor argument at one call site is a different class of instrument, and it is the transferable idea here even for teams that never install the package.
Ranked by verification strength, evidence, and original report placement.
bloc_signals 1.1.0 introduces universal, symmetrical adapter extensions that allow any Dart Future, Stream, ReadonlySignal, or lifted primitive (value.$) to adapt into a synchronous BlocSignalBase container with a single method call.
The API follows a dual-track principle: .toBlocSignal(...) yields BlocSignalBase<T> holding raw domain state, and .toAsyncBlocSignal(...) yields BlocSignalBase<AsyncState<T>>.
The AsyncState track provides first-class lifecycle tracking (AsyncLoading to AsyncData or AsyncError) with exhaustive pattern matching.
Because signals in the signals_core graph always hold an immediate synchronous value, converting any signal to a BlocSignalBase requires no arguments.
Futures do not have a synchronous value on frame 0, so Future.toBlocSignal() requires an explicit initialState:.
In the post's example, api.fetchUserProfile(userId).toBlocSignal(initialState: User.anonymous()) makes userBloc.stateValue immediately User.anonymous() on frame 0, and as soon as the future completes it emits the resolved User synchronously into the state machine.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 28, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
The duplicate snackbar is a modelling error, and the fix belongs in the state layer1 distinct publisher
build
Split Flutter CI from CD, or pay macOS rates on every pull request1 distinct publisher
build
One non-ASCII letter broke four toolchains: path checks belong in CI, not folklore1 distinct publisher
build
A key in the app binary is a bucket handover; presigned uploads also drop the proxy data bill1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Concrete code, single-hand sourcing
The API detail is unusually specific for a launch post — real signatures, real return types, a frame-zero rule you can reason about — and that specificity is why the technical claims read as credible. But every one of them traces to the package author's own words on dev.to. There is no changelog, no pub listing, no second reader, and the copy we hold breaks off mid-word, so the behavioural promises about effect teardown and error routing rest on assertion alone.
A release announcement, and nothing after it
What exists is the announcement itself. No install counts, no dependent packages, no team saying they migrated a screen off FutureBuilder, not even a pub.dev version page. The before/after comparison that closes the post pits hand-written ChangeNotifier plumbing against one line of the author's API — a rhetorical contrast, not a record of anyone using it.
"Disappears completely" outruns the receipts
Positive, and the gap sits in the adjectives rather than the code. "Universal", "symmetrical" and "disappears completely" are absolute words attached to a minor version with no users on record, and the universality claim is already leaky — the async track is documented only for futures and streams. Strip the superlatives and what remains is an honest, modest convenience layer that would have survived a quieter description.
The author ships the package and wrote the indictment
The same person supplies the problem and the cure. FutureBuilder is declared an anti-pattern by reference to the author's own earlier posts and his own video, the corrective history turns on an issue he says he filed, and the fix is his package's new release. That is not concealed — dev.to's post says "we have introduced" — but it does mean the diagnosis and the remedy have never been separated by an independent hand.
Confident about the shapes, not the behaviour
We can be fairly sure what the methods are called and what they return, because the snippets are there. We cannot be sure the runtime behaves as promised, that the matrix is as complete as claimed, or that the missing tail of the post does not qualify any of it. Our earlier note about where the text cut off turned out to be wrong, which is its own argument for holding this loosely.