Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build cross-platform Flutter 3 apps with Riverpod/Bloc state management, GoRouter navigation, and performance optimization.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/bloc-state.md
1# Bloc State Management23## When to Use Bloc45Use **Bloc/Cubit** when you need:67* Explicit event → state transitions8* Complex business logic9* Predictable, testable flows10* Clear separation between UI and logic1112| Use Case | Recommended |13| ---------------------- | ----------- |14| Simple mutable state | Riverpod |15| Event-driven workflows | Bloc |16| Forms, auth, wizards | Bloc |17| Feature modules | Bloc |1819---2021## Core Concepts2223| Concept | Description |24| ------- | ---------------------- |25| Event | User/system input |26| State | Immutable UI state |27| Bloc | Event → State mapper |28| Cubit | State-only (no events) |2930---3132## Basic Bloc Setup3334### Event3536```dart37sealed class CounterEvent {}3839final class CounterIncremented extends CounterEvent {}4041final class CounterDecremented extends CounterEvent {}42```4344### State4546```dart47class CounterState {48final int value;4950const CounterState({required this.value});5152CounterState copyWith({int? value}) {53return CounterState(value: value ?? this.value);54}55}56```5758### Bloc5960```dart61import 'package:flutter_bloc/flutter_bloc.dart';6263class CounterBloc extends Bloc<CounterEvent, CounterState> {64CounterBloc() : super(const CounterState(value: 0)) {65on<CounterIncremented>((event, emit) {66emit(state.copyWith(value: state.value + 1));67});6869on<CounterDecremented>((event, emit) {70emit(state.copyWith(value: state.value - 1));71});72}73}74```7576---7778## Cubit (Recommended for Simpler Logic)7980```dart81class CounterCubit extends Cubit<int> {82CounterCubit() : super(0);8384void increment() => emit(state + 1);85void decrement() => emit(state - 1);86}87```8889---9091## Providing Bloc to the Widget Tree9293```dart94BlocProvider(95create: (_) => CounterBloc(),96child: const CounterScreen(),97);98```99100Multiple blocs:101102```dart103MultiBlocProvider(104providers: [105BlocProvider(create: (_) => AuthBloc()),106BlocProvider(create: (_) => ProfileBloc()),107],108child: const AppRoot(),109);110```111112---113114## Using Bloc in Widgets115116### BlocBuilder (UI rebuilds)117118```dart119class CounterScreen extends StatelessWidget {120const CounterScreen({super.key});121122@override123Widget build(BuildContext context) {124return BlocBuilder<CounterBloc, CounterState>(125buildWhen: (prev, curr) => prev.value != curr.value,126builder: (context, state) {127return Text(128state.value.toString(),129style: Theme.of(context).textTheme.displayLarge,130);131},132);133}134}135```136137---138139### BlocListener (Side Effects)140141```dart142BlocListener<AuthBloc, AuthState>(143listenWhen: (prev, curr) => curr is AuthFailure,144listener: (context, state) {145if (state is AuthFailure) {146ScaffoldMessenger.of(context)147.showSnackBar(SnackBar(content: Text(state.message)));148}149},150child: const LoginForm(),151);152```153154---155156### BlocConsumer (Builder + Listener)157158```dart159BlocConsumer<FormBloc, FormState>(160listener: (context, state) {161if (state.status == FormStatus.success) {162context.pop();163}164},165builder: (context, state) {166return ElevatedButton(167onPressed: state.isValid168? () => context.read<FormBloc>().add(FormSubmitted())169: null,170child: const Text('Submit'),171);172},173);174```175176---177178## Accessing Bloc Without Rebuilds179180```dart181context.read<CounterBloc>().add(CounterIncremented());182```183184⚠️ **Never use `watch` inside callbacks**185186---187188## Async Bloc Pattern (API Calls)189190```dart191on<UserRequested>((event, emit) async {192emit(const UserState.loading());193194try {195final user = await repository.fetchUser();196emit(UserState.success(user));197} catch (e) {198emit(UserState.failure(e.toString()));199}200});201```202203---204205## Bloc + GoRouter (Auth Guard Example)206207```dart208redirect: (context, state) {209final authState = context.read<AuthBloc>().state;210211if (authState is Unauthenticated) {212return '/login';213}214return null;215}216```217218---219220## Testing Bloc221222```dart223blocTest<CounterBloc, CounterState>(224'emits incremented value',225build: () => CounterBloc(),226act: (bloc) => bloc.add(CounterIncremented()),227expect: () => [228const CounterState(value: 1),229],230);231```232233---234235## Best Practices (MUST FOLLOW)236237✅ Immutable states238✅ Small, focused blocs239✅ One feature = one bloc240✅ Use Cubit when possible241✅ Test all blocs242243❌ No UI logic inside blocs244❌ No context usage inside blocs245❌ No mutable state246❌ No massive “god blocs”247248---249250## Quick Reference251252| Widget | Purpose |253| ----------------- | -------------------- |254| BlocBuilder | UI rebuild |255| BlocListener | Side effects |256| BlocConsumer | Both |257| BlocProvider | Dependency injection |258| MultiBlocProvider | Multiple blocs |259260