Build1 distinct publisher3 min readUpdated
A Flutter post argues one-shot effects do not belong in persistent state, and that a synchronous state layer removes the need for a presentation-stream package.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Follow any of these and your For You feed starts watching them — no settings page required.
A dev.to post on BlocSignal reopens a familiar Flutter failure: authentication fails, the container emits an error, the listener shows a SnackBar, and then the user rotates the phone, the tree rebuilds, the container is still holding `AuthErrorState("Invalid password")`, the listener fires again, and a second snackbar appears [1]. This matters less as a rendering annoyance than as a modelling result: the container is doing exactly what a container is for, which is surviving rebuilds and handing its current value to whoever asks [4], so the replay is the design working, not the design breaking [15].
The post's taxonomy is the useful part. Persistent state answers what the system's condition is now - logged in, loading, cart total [2]. Presentation actions are pulses that answer what just happened and needs a single reaction: a toast, a confirmation dialog, a route push, a haptic buzz [3]. Persistent state is compared by equality and outlives rebuilds; an effect is consumed once, is triggered by an event, and should leave no domain footprint [4]. Once you write those two columns down, the duplicate snackbar stops being mysterious.
Three historical workarounds are enumerated [13]. Emit the failure and then immediately emit `AuthInitial()`, which the post says costs two microtask ticks and multiple rebuild cycles just to clear a transient flag [5]. Or carry `errorSnackbarMessage` and a `hasShownSnackbar` bool inside the state class, which the post calls manual bookkeeping that clutters domain types with imperative UI tracking [6]. Or adopt LeanCode's `package:bloc_presentation`, which bolts a second broadcast StreamController onto a Bloc so `emitPresentation(MyEvent())` runs independently of `emit(state)` [7]. Two of the three mutate domain state to carry a UI event; the third moves the boundary out of your codebase and into a dependency [16]. The post credits `bloc_presentation` with solving the problem for classic BLoC while objecting to the versioning churn, boilerplate and dependency overhead of maintaining wrapper packages in a monorepo [8].
The claim underneath BlocSignal's alternative is a timing claim: `emit(newState)` updates the signal and settles dependencies in the same frame, rather than queueing on microtask-based streams as classic BLoC does [9]. If that holds, the recommended pattern needs no channel at all: await `cubit.signIn(...)`, guard with `if (!context.mounted) return`, then pattern-match on `cubit.stateValue` to push a route on success or show a snackbar on failure [10]. The post's argument for it is that `stateValue` is current the instant the call returns, that rotations and unrelated rebuilds never re-run a button handler, and that no listeners, streams or consumable wrapper classes are required [11], with zero additional package dependencies [14].
Read the sample carefully and the boundary has moved to the call site rather than disappeared: the branch lives in the widget callback and is triggered by the interaction, not by observing state [17]. That is fine for taps and useless for effects the domain raises on its own, which the post acknowledges - a WebSocket disconnect, a finished background sync, or an in-progress migration off `bloc_presentation` - and it offers a compatible drop-in for those cases, at which point the supplied excerpt cuts off [12].
Worth watching: whether the compatible path for autonomous effects is genuinely drop-in, since that is where the second channel reappears inside the library instead of beside it [12]; and whether same-frame settling holds under real load, because every claimed property in the pattern rests on it [9][11]. All of the above comes from one post about its own library, unverified here.
Ranked by verification strength, evidence, and original report placement.
The post's headline and introduction claim BlocSignal lets developers handle one-shot side effects cleanly with zero additional package dependencies.
The post describes the "Sticky State Dilemma": on authentication failure the state container emits an error and the UI shows a SnackBar; when the user rotates the phone, pulls down the notification shade, or types on the virtual keyboard, the widget tree rebuilds, the container is still holding AuthErrorState("Invalid password"), the UI listener fires again, and a duplicate snackbar appears.
The post states that state management in Flutter is designed to model persistent truth over time, giving examples such as whether the user is logged in (AuthState.authenticated(user)), whether data is loading (TodoState.loading), and the cart total ($49.99), and that persistent state answers "What is the system's current condition?"
The post describes UI presentation actions as ephemeral pulses: show a brief SnackBar toast, pop up an alert confirmation dialog, push a new route on the Navigator stack, vibrate the haptic motor; these answer "What just happened that requires a one-time reaction?"
The post's comparison table says persistent state is survived by UI rebuilds, represented in signals, and backed by equality diffs, while an ephemeral side-effect is consumed once and gone, triggered by an event, and has zero domain state footprint.
The first historical workaround shown is emitting a reset state immediately after an error: emit(AuthFailure(error)); emit(AuthInitial()); the post says this causes two separate microtask queue ticks and multiple widget rebuild cycles just to reset a transient flag.
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.
Single self-interested source with code, no verification
The cluster contains one dev.to tutorial. Its descriptive content — the duplicate-snackbar mechanism, the state/effect taxonomy, the three historical BLoC workarounds, and the recommended pattern — is legible and shown in code, which is genuine evidence about the pattern's shape. But every differentiating technical claim (synchronous same-frame propagation, zero race conditions, a 100%-compatible drop-in) is prose assertion with no tests, benchmarks, library internals or second source, and the listener sample is truncated mid-class.
No adoption signal in supplied material
The sources provide no downloads, dependents, production deployments, releases, benchmarks or usage disclosures for BlocSignal, bloc_signals_flutter or bloc_presentation. The only ecosystem reference is that LeanCode authored bloc_presentation for classic BLoC users, which describes existence rather than measured uptake, so no adoption level can be scored without guessing.
Absolute guarantees outrun the shown evidence
The post reaches for unqualified language — zero race conditions, state '100% up to date', zero extra code, a '100% compatible' drop-in, zero additional dependencies — while supplying no measurement, no adoption evidence and an argument that still reads cubit.stateValue at the call site. The underlying modelling insight is sound and well illustrated, so the gap is moderate overstatement of certainty rather than a fabricated claim.
Advocacy post for the library it recommends
The article is structured as a funnel toward BlocSignal: it frames the incumbent third-party solution as versioning churn and dependency overhead without data, then presents BlocSignal's synchronous propagation as making presentation streams unnecessary, and closes with bloc_signals-importing code. The publisher is a community platform with no editorial separation from the author, and no relationship disclosure is offered, so the promotional incentive is strong and structural even though the technical framing is substantive.
Low: one advocacy source, no adoption data, truncated text
Confidence is limited by single-source coverage, the absence of any adoption or independent verification, a strong promotional incentive, and truncation of the final code sample. What can be held with reasonable confidence is narrow: the problem description, the taxonomy, the prior-art enumeration, and the shape of the recommended pattern.
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
The Enter key bug is a Focus bug: why wrapping a Flutter TextField in Focus always loses1 distinct publisher
build
App Review rejections are design decisions you made six weeks earlier1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 15, 2026