Files
minimax-skills/skills/flutter-dev/references/bloc-state.md
mljxxx 2995582a5e feat: add flutter-dev skill
Add comprehensive Flutter cross-platform development guide covering:
- Widget patterns and const optimization
- Riverpod/Bloc state management
- GoRouter navigation
- Performance optimization
- Testing strategies
- Platform-specific implementations

Made-with: Cursor
2026-03-26 17:35:55 +08:00

282 lines
6.6 KiB
Markdown

# Bloc State Management
Bloc state management guide covering events, states, Cubit, and widget integration for complex business logic.
## When to Use Bloc
Use **Bloc/Cubit** when you need:
- Explicit event → state transitions
- Complex business logic with multiple events
- Predictable, testable state flows
- Clear separation between UI and logic
| Use Case | Recommended |
|----------|-------------|
| Simple mutable state | Riverpod |
| Computed values | Riverpod |
| Event-driven workflows | Bloc |
| Forms, auth, wizards | Bloc |
| Feature modules with complex logic | Bloc |
## Core Concepts
| Concept | Description |
|---------|-------------|
| Event | User or system input that triggers state change |
| State | Immutable representation of UI state |
| Bloc | Maps events to new states |
| Cubit | Simplified Bloc without events |
## Cubit (Recommended for Simpler Logic)
```dart
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
void reset() => emit(0);
}
```
## Full Bloc Setup
### Event Definition
```dart
sealed class CounterEvent {}
final class CounterIncremented extends CounterEvent {}
final class CounterDecremented extends CounterEvent {}
final class CounterReset extends CounterEvent {}
```
### State Definition
```dart
class CounterState {
final int value;
final bool isLoading;
const CounterState({
required this.value,
this.isLoading = false,
});
CounterState copyWith({int? value, bool? isLoading}) {
return CounterState(
value: value ?? this.value,
isLoading: isLoading ?? this.isLoading,
);
}
}
```
### Bloc Implementation
```dart
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(value: 0)) {
on<CounterIncremented>(_onIncremented);
on<CounterDecremented>(_onDecremented);
on<CounterReset>(_onReset);
}
void _onIncremented(CounterIncremented event, Emitter<CounterState> emit) {
emit(state.copyWith(value: state.value + 1));
}
void _onDecremented(CounterDecremented event, Emitter<CounterState> emit) {
emit(state.copyWith(value: state.value - 1));
}
void _onReset(CounterReset event, Emitter<CounterState> emit) {
emit(const CounterState(value: 0));
}
}
```
## Providing Bloc to Widget Tree
```dart
// Single bloc
BlocProvider(
create: (_) => CounterBloc(),
child: const CounterScreen(),
);
// Multiple blocs
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => AuthBloc()),
BlocProvider(create: (_) => ProfileBloc()),
BlocProvider(create: (_) => SettingsBloc()),
],
child: const AppRoot(),
);
```
## Using Bloc in Widgets
### BlocBuilder (UI Rebuilds)
```dart
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, CounterState>(
buildWhen: (prev, curr) => prev.value != curr.value,
builder: (context, state) {
return Text(
state.value.toString(),
style: Theme.of(context).textTheme.displayLarge,
);
},
);
}
}
```
### BlocListener (Side Effects)
```dart
BlocListener<AuthBloc, AuthState>(
listenWhen: (prev, curr) => prev.status != curr.status,
listener: (context, state) {
if (state.status == AuthStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Error')),
);
}
if (state.status == AuthStatus.authenticated) {
context.go('/home');
}
},
child: const LoginForm(),
);
```
### BlocConsumer (Builder + Listener)
```dart
BlocConsumer<FormBloc, FormState>(
listenWhen: (prev, curr) => prev.status != curr.status,
listener: (context, state) {
if (state.status == FormStatus.success) {
context.pop();
}
},
buildWhen: (prev, curr) => prev.isValid != curr.isValid,
builder: (context, state) {
return ElevatedButton(
onPressed: state.isValid
? () => context.read<FormBloc>().add(FormSubmitted())
: null,
child: const Text('Submit'),
);
},
);
```
### BlocSelector (Granular Rebuilds)
```dart
BlocSelector<UserBloc, UserState, String>(
selector: (state) => state.user.name,
builder: (context, name) {
return Text('Hello, $name');
},
);
```
## Async Bloc Pattern
```dart
on<UserRequested>((event, emit) async {
emit(state.copyWith(status: UserStatus.loading));
try {
final user = await repository.fetchUser(event.userId);
emit(state.copyWith(status: UserStatus.success, user: user));
} catch (e) {
emit(state.copyWith(status: UserStatus.failure, error: e.toString()));
}
});
```
## Bloc + GoRouter Auth Guard
```dart
redirect: (context, state) {
final authState = context.read<AuthBloc>().state;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
if (authState.status != AuthStatus.authenticated && !isAuthRoute) {
return '/auth/login';
}
if (authState.status == AuthStatus.authenticated && isAuthRoute) {
return '/';
}
return null;
}
```
## Testing Bloc
```dart
import 'package:bloc_test/bloc_test.dart';
blocTest<CounterBloc, CounterState>(
'emits incremented value when CounterIncremented added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(CounterIncremented()),
expect: () => [const CounterState(value: 1)],
);
blocTest<CounterBloc, CounterState>(
'emits multiple states',
build: () => CounterBloc(),
act: (bloc) {
bloc.add(CounterIncremented());
bloc.add(CounterIncremented());
bloc.add(CounterDecremented());
},
expect: () => [
const CounterState(value: 1),
const CounterState(value: 2),
const CounterState(value: 1),
],
);
```
## Best Practices
| Do | Don't |
|----|-------|
| Keep states immutable | Mutate state directly |
| Use small, focused blocs | Create "god blocs" with everything |
| One feature = one bloc | Share blocs across unrelated features |
| Use Cubit for simple cases | Overcomplicate with Bloc unnecessarily |
| Test all state transitions | Skip bloc testing |
| Use `buildWhen`/`listenWhen` | Rebuild on every state change |
## Widget Reference
| Widget | Purpose |
|--------|---------|
| `BlocBuilder` | UI rebuilds based on state |
| `BlocListener` | Side effects (navigation, snackbar) |
| `BlocConsumer` | Both builder and listener |
| `BlocSelector` | Granular state selection |
| `BlocProvider` | Dependency injection |
| `MultiBlocProvider` | Multiple bloc injection |
| `RepositoryProvider` | Repository injection |
---
*Bloc is an open-source state management library by Felix Angelov.*