跪拜 Guibai
← All articles
Flutter · Android · Architecture

Flutter Zero: A go-zero-Inspired CLI That Generates Full MVI-BLoC Modules

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

Flutter projects accumulate repetitive boilerplate quickly, and most existing scaffolds stop at empty folders. Flutter Zero generates a complete, opinionated MVI-BLoC module with production concerns like request cancellation, error normalization, and side-effect isolation already wired in, cutting the setup time for a new feature to a single command.

Summary

A new open-source CLI, fluzer, scaffolds entire Flutter feature modules with a single command, generating BLoC, Repository, Effect handlers, and DI registration. The architecture centers on four independent Mixins that separate concerns within flutter_bloc: BlocAwaitMixin bridges async operations to the UI with Futures, BlocEffectMixin isolates side effects onto a dedicated Stream, BlocCancelTokenMixin ties network request lifecycles to page navigation, and BlocErrorHandlerMixin normalizes errors into a typed Result pattern.

The side-effect system uses an open abstract class and a chain of responsibility, allowing any feature to define custom UI effects without modifying framework code. A companion `gen-l10n` command parses standard .arb files to generate type-safe localization identifiers, decoupling translation logic from the BLoC layer entirely. The DI layer uses a template method pattern with AST-based code injection, ensuring the CLI can add new module registrations idempotently.

Built by an Android developer who missed go-zero's code generation in the Dart ecosystem, the toolchain prioritizes eliminating boilerplate and preventing silent failures—Completers time out after 30 seconds, BLoC disposal cancels all pending requests, and runtime checks throw immediately if a required Mixin is missing.

Takeaways
Running `fluzer new user_center` generates a full feature module with BLoC, freezed state/event, Repository, Effect handler, Page, and DI registration in one shot.
BlocAwaitMixin uses a Completer map to let the UI `await` BLoC event completion, with a 30-second timeout and automatic cleanup on BLoC close to prevent hanging Futures.
BlocEffectMixin puts side effects (Toasts, Dialogs, Loading) on a broadcast Stream, separating them from immutable State and preventing single-slot overwrite bugs.
The side-effect listener uses a chain of responsibility: business handlers run first, and framework fallbacks for Toast, Dialog, and Loading run last if no handler claims the effect.
BlocCancelTokenMixin maintains a map of named CancelTokens; calling `token('search')` cancels the previous request under that key before creating a new one, enabling automatic debounce.
BlocErrorHandlerMixin wraps async operations in a `Result<T>` type with three states—Success, Failure, and Cancel—forcing explicit handling of cancellation versus errors.
Error normalization converts DioExceptions into a typed AppException tree, and a configurable `ServerMessageExtractor` strategy parses server error messages from different JSON field names.
The `gen-l10n` command scans .arb files and generates type-safe `L10nCode` factory constructors, so BLoCs emit semantic identifiers instead of hardcoded strings or context-dependent translations.
DI registration uses a template method pattern; the CLI injects new module registrations via AST manipulation, guaranteeing idempotency so duplicate lines are never inserted.
Repository ownership follows a simple rule: single-feature repos stay in the feature's data directory; shared repos move to core and register through `SharesRepositories` to prevent cross-feature imports.
Conclusions

Using an abstract class instead of a sealed class for UIEffect is a deliberate trade-off: it sacrifices compile-time exhaustiveness checking to allow any feature to define custom side effects without modifying framework source files.

The `onAwait` method that wraps event handlers in try/finally is a direct response to a common failure mode—developers forgetting to call `completeAwait` after early returns or caught exceptions—and mirrors go-zero's philosophy of eliminating anything that can be forgotten.

Making the `cancel` parameter optional in `Result.when()` acknowledges that most business scenarios genuinely need no action on cancellation; forcing an empty callback would add noise across dozens of BLoCs.

Placing all freezed imports in a single main BLoC file and using `part of` for state and event definitions prevents code generation failures caused by missing dependencies in subsidiary files, a practical fix for a common freezed pain point.

The ToastEffect priority chain (message > l10nCode > code) cleanly separates three sources of user-facing text—server-translated, client-localized, and error-code fallback—without the BLoC ever needing to know the current locale.

Requiring `BlocEffectMixin` on the BLoC and throwing a StateError at build time if it's missing turns a silent failure (effects mysteriously not appearing) into an immediate, debuggable crash.

Concepts & terms
MVI Architecture
Model-View-Intent, a unidirectional data flow pattern where user actions are modeled as Intents (Events) that drive State changes, and the View renders State immutably. Side effects like Toasts travel through a separate channel.
BLoC Pattern
Business Logic Component, a state management pattern in Flutter that uses Streams to separate presentation from business logic. Events go in, States come out, and the UI rebuilds reactively.
Mixin
A Dart language feature that allows a class to reuse code from multiple sources without traditional inheritance. Mixins can be composed together and can specify a required superclass with the `on` keyword.
Completer
A Dart object that allows producing a Future and completing it later, bridging callback-based or event-based code with async/await syntax.
Chain of Responsibility
A behavioral design pattern where a request passes through a chain of handlers; each handler decides whether to process the request or pass it to the next handler in the chain.
Dependency Inversion Principle
The 'D' in SOLID: high-level modules should not depend on low-level modules; both should depend on abstractions. Here, BLoCs depend on the abstract UIEffect and ToastService, not on concrete Toast libraries.
freezed
A Dart code generator for immutable classes, sealed unions, and pattern matching. It generates `copyWith`, `==`, `hashCode`, and `toString` from simple factory constructor definitions.
CancelToken
A Dio HTTP client object that, when cancelled, aborts the associated network request. Used here to tie request lifecycles to page navigation and to deduplicate rapid-fire requests like search.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗