跪拜 Guibai
← All articles
Flutter · Architecture · Android

Flutter Zero: A go-zero-Inspired MVI-BLoC Scaffold That Generates Your Entire Feature Module

By 年小个大 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Flutter developers starting new projects repeatedly write the same BLoC boilerplate, wire up dependency injection, and struggle with side-effect management. This toolchain automates the scaffolding and provides a composable Mixin architecture that solves common pain points—like awaiting event completion, canceling in-flight requests on page exit, and keeping side effects out of State—without forcing a rigid, one-size-fits-all framework.

Summary

Flutter Zero is a toolchain that automates the creation of feature modules in Flutter, inspired by Go's go-zero framework. Its CLI, `fluzer`, generates a full set of files for a feature, including a BLoC with four specialized Mixins for awaiting events, managing side effects, canceling network requests, and handling errors through a Kotlin-style `Result` type. The architecture strictly separates UI state from one-time side effects like Toasts and dialogs using an independent Stream and a chain-of-responsibility pattern, allowing new effect types to be added without modifying framework code. The system also includes a type-safe internationalization code generator that defers translation to the UI layer, keeping BLoCs free of context dependencies. Dependency injection is managed through a template method pattern that automatically registers new modules via AST manipulation, and network request lifecycles are tied to page lifecycles to prevent zombie callbacks.

Takeaways
Running `fluzer new user_center` generates a complete feature module including BLoC, State, Event, Repository, Effect handler, Page, and DI registration, and automatically inserts the module into the injection base file.
Four independent Mixins—BlocAwaitMixin, BlocEffectMixin, BlocCancelTokenMixin, and BlocErrorHandlerMixin—can be composed into a BLoC to add only the needed capabilities, such as making events awaitable from the UI or auto-cancelling Dio requests on page disposal.
Side effects like Toasts and dialogs are emitted on a separate broadcast Stream instead of being stuffed into the immutable State object, preventing single-slot overwrites and eliminating the need for manual cleanup.
New side-effect types can be added by extending the abstract `UIEffect` class and adding a handler to the chain of responsibility, requiring zero changes to the framework's core code.
The `runCatching` method wraps asynchronous operations in a `Result<T>` sealed class with `Success`, `Failure`, and `Cancel` subtypes, forcing explicit handling of cancellation as a distinct outcome from failure.
Internationalized text is triggered from the BLoC using a type-safe `L10nCode` identifier, deferring the actual translation to the UI layer so the BLoC never needs a `BuildContext` or knowledge of the current locale.
Dio is not registered in the base DI layer but in each environment's `main` entry point, allowing different base URLs and interceptor sets for dev, staging, and production without changing infrastructure code.
A Repository is placed in a feature's own `data` directory if used by only one feature; once shared by two or more, it moves to `core/data` to prevent features from importing each other's internal implementations.
Conclusions

Manually calling `completeAwait` in a BLoC's event handler is a leaky abstraction because an early return or a missed finally block can hang a Future indefinitely; the `onAwait` wrapper eliminates this by guaranteeing completion in a finally block.

Using an abstract class instead of a sealed class for `UIEffect` is a deliberate trade-off: it sacrifices compile-time exhaustiveness checking in favor of allowing any feature module to define custom side effects without modifying the framework's core library.

Refusing to normalize exceptions into a custom framework exception tree avoids making incorrect business assumptions about error codes and message formats, but it shifts the burden of consistent error handling onto individual BLoC implementations.

The `BlocCancelTokenMixin` design, where requesting a token with the same key automatically cancels the previous one, bakes a debounce-like deduplication directly into the network layer without requiring explicit cancel logic in business code.

Generating L10nCode factory constructors from `.arb` files makes the localization call-site type-safe, catching missing or misspelled keys at compile time rather than at runtime, which is a significant ergonomic improvement over raw string keys.

Concepts & terms
MVI (Model-View-Intent)
An architectural pattern where user actions are represented as Intents (Events) that drive changes to an immutable Model (State), which the View renders unidirectionally. Side effects are handled through a separate channel.
BLoC (Business Logic Component)
A state management pattern in Flutter that separates business logic from the UI. It receives Events, processes them, and emits States. The flutter_bloc library is a popular implementation.
Mixin
A Dart language feature that allows a class to reuse code from multiple sources without traditional inheritance. In this architecture, orthogonal capabilities like error handling and effect management are composed into a BLoC via Mixins.
Freezed
A Dart code generation package that creates immutable data classes with copyWith, pattern matching, and JSON serialization. It is used here to define BLoC States and Events as sealed unions.
Chain of Responsibility
A behavioral design pattern where a request is passed along a chain of handlers, and each handler decides whether to process it or pass it to the next. Used here to allow business code to intercept or override default side-effect handling.
CancelToken
A Dio HTTP client object used to cancel in-progress network requests. The BlocCancelTokenMixin manages a map of named tokens, cancelling them automatically when a page is disposed.
Result<T>
A sealed class with three subtypes—Success, Failure, and Cancel—that explicitly models the outcome of an operation. It forces callers to handle the cancellation case distinctly from a failure.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗