flutter-state
Skill12 starsChanged 55 days agoMITView on GitHub
Use when implementing or reviewing Flutter state management with Cubit - a sealed status class with variants, BlocBuilder with pattern matching, a Cubit fed by a Stream for realtime data, or an event bus for cross-module communication. Triggers when a screen has to react to data that changes.
In this repository15 other files it gives its agents.
- flutter-accessibilitySkill
- flutter-cachingSkill
- flutter-clean-archSkill
- flutter-concurrencySkill
- flutter-dart-styleSkill
- flutter-design-systemSkill
- flutter-diSkill
- flutter-formsSkill
- flutter-monorepoSkill
- flutter-new-moduleSkill
- flutter-runSkill
- flutter-store-cdSkill
- flutter-testingSkill
- flutter-themingSkill
- flutter-webSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 4 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 201
Use abstract class (not sealed — Dart sealed requires same file)
- Distinctive: line 206
Never embed logic in status classes — only data
- Very specific: line 212
Always emit(Loading()) before async calls
- Distinctive: line 213
Always wrap async in try/catch → emit Failure
- Very specific: line 215
Never call emit() after close() — check !isClosed if needed
- Distinctive: line 219
Read the existing controller in the module if one exists
- Very specific: line 221
Create _status.dart and _controller.dart files
- Very specific: line 222
Update the module's DI registration (see /flutter-di skill)
- Very specific: line 223
Update the screen to use BlocBuilder or BlocListener appropriately
- Distinctive: line 224
Use switch expressions for status handling in UI (cleaner than if/else)
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
You are implementing state management for a Flutter feature using Cubit + RxDart.
Three Patterns by Use Case
Choose based on the skill argument, if one was given, or on the context of the feature:
simple— basic async operations (load data, submit form)stream— real-time data via RxDart BehaviorSubjectevent-bus— cross-module communication via Broker
Pattern 1: Simple Cubit (default)
Best for: load list, submit form, CRUD
<feature>_status.dart:
abstract class <Feature>Status {
const <Feature>Status();
}
class <Feature>Initial extends <Feature>Status {
const <Feature>Initial();
}
class <Feature>Loading extends <Feature>Status {
const <Feature>Loading();
}
class <Feature>Success extends <Feature>Status {
final List<<Entity>> items;
const <Feature>Success(this.items);
}
class <Feature>Failure extends <Feature>Status {
final String message;
const <Feature>Failure(this.message);
}<feature>_controller.dart:
import 'package:flutter_bloc/flutter_bloc.dart';
import '<feature>_status.dart';
class <Feature>Controller extends Cubit<<Feature>Status> {
final Get<Feature>Usecase _get<Feature>;
<Feature>Controller(this._get<Feature>)
: super(const <Feature>Initial());
Future<void> load() async {
emit(const <Feature>Loading());
try {
final items = await _get<Feature>.call();
emit(<Feature>Success(items));
} catch (e) {
emit(<Feature>Failure(e.toString()));
}
}
Future<void> refresh() => load();
}In screen:
BlocBuilder<<Feature>Controller, <Feature>Status>(
builder: (context, status) => switch (status) {
<Feature>Loading() => const Center(child: CircularProgressIndicator()),
<Feature>Failure(:final message) => Center(child: Text(message)),
<Feature>Success(:final items) => _buildList(items),
_ => const SizedBox.shrink(),
},
),Pattern 2: Stream Cubit (real-time)
Best for: live order updates, command status, POS terminal events
<feature>_controller.dart:
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rxdart/rxdart.dart';
import '<feature>_status.dart';
class <Feature>Controller extends Cubit<<Feature>Status> {
final <Feature>Repository _repository;
final _subject = BehaviorSubject<List<<Entity>>>();
StreamSubscription? _sub;
<Feature>Controller(this._repository) : super(const <Feature>Initial());
Stream<List<<Entity>>> get stream => _subject.stream;
Future<void> watch() async {
emit(const <Feature>Loading());
try {
_sub = _repository
.watchAll()
.debounceTime(const Duration(milliseconds: 300))
.listen(
(items) {
_subject.add(items);
emit(<Feature>Success(items));
},
onError: (e) => emit(<Feature>Failure(e.toString())),
);
} catch (e) {
emit(<Feature>Failure(e.toString()));
}
}
@override
Future<void> close() {
_sub?.cancel();
_subject.close();
return super.close();
}
}Pattern 3: Event Bus (cross-module)
Best for: payment completion → order refresh, command events, notifications
Publishing an event (in sender controller):
class PaymentController extends Cubit<PaymentStatus> {
final Broker _broker;
PaymentController(this._broker, ...) : super(const PaymentInitial());
Future<void> confirmPayment(String commandId) async {
// ... process payment
_broker.dispatch(BrokerEvent(
kind: 'payment.confirmed',
data: {'commandId': commandId},
));
}
}Consuming an event (in receiver controller):
class CommandController extends Cubit<CommandStatus> {
final Broker _broker;
StreamSubscription? _paymentSub;
CommandController(this._broker, ...) : super(const CommandInitial()) {
_listenToPayments();
}
void _listenToPayments() {
_paymentSub = _broker
.on('payment.confirmed')
.listen((event) => refresh());
}
@override
Future<void> close() {
_paymentSub?.cancel();
return super.close();
}
}Consumer widget (subscribes during module load):
class PaymentConsumer {
final Broker broker;
StreamSubscription? _sub;
PaymentConsumer({required this.broker});
void start() {
_sub = broker.on('order.created').listen((event) {
// react to event
});
}
void dispose() => _sub?.cancel();
}Status State Design Rules
- Use
abstract class(not sealed — Dart sealed requires same file) - Each status is a separate class extending the abstract base
- States are immutable — use
constconstructors - Success state carries data as
finalfields - Failure state carries only
final String message - Never embed logic in status classes — only data
Controller Rules
- Extends
Cubit<T>— notBloc<Event, State>(simpler for most cases) - Methods are
Future<void>for async,voidfor sync - Always
emit(Loading())before async calls - Always wrap async in try/catch → emit Failure
- Cancel all subscriptions in
close() - Never call
emit()afterclose()— check!isClosedif needed
Instructions
- Read the existing controller in the module if one exists
- Choose the right pattern based on whether the feature needs real-time data or cross-module events
- Create
_status.dartand_controller.dartfiles - Update the module's DI registration (see
/flutter-diskill) - Update the screen to use
BlocBuilderorBlocListenerappropriately - Use switch expressions for status handling in UI (cleaner than if/else)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

