Irbisa · Cheatsheet
State with BLoC
BLoC pattern, Cubit, Events, States, BlocBuilder, BlocConsumer
What is the BLoC pattern, and what is the difference between
BlocandCubit?// Cubit: input is a method callclass CounterCubit extends Cubit<int> { CounterCubit() : super(0); void increment() => emit(state + 1);}…What do
BlocBuilder,BlocListener,BlocConsumerandBlocSelectordo, and what arebuildWhenandlistenWhenfor?// rebuilds only when the item count changesBlocBuilder<CartBloc, CartState>( buildWhen: (prev, curr) => prev.items.length != curr.items.length, builder: (context, state) => Badge(label: Text('${state.items.length}')),);…Find the bug: typing in the search field rebuilds the open-todos list, though
state.todosis the same list.class OpenTodos extends StatelessWidget { const OpenTodos({super.key}); @override Widget build(BuildContext context) { return BlocSelector<TodoCubit, TodoState, List<Todo>>( selector: (state) => state.todos.where((t) => !t.done).toList(), builder: (context, open) => ListView.builder( itemCount: open.length, itemBuilder: (context, i) => TodoTile(open[i]), ), ); }}What is the difference between
BlocProvider(create:)andBlocProvider.value, and how do you pass an existing bloc to a pushed route?// owner: builds the bloc and closes it when this subtree goes awayBlocProvider( create: (context) => CartBloc(context.read<CartRepository>()), child: const CartPage(),);…Why does BLoC need value equality (
Equatable,freezed) for states, and what goes wrong without it?// before: no value equality, identity decidesfinal class ProfileLoaded extends ProfileState { ProfileLoaded(this.user); final User user;}// emit(ProfileLoaded(u)); emit(ProfileLoaded(u));…This Cubit adds an item, but after the first one the cart on screen stops updating. Why?
class CartState extends Equatable { const CartState(this.items); final List<Item> items; @override List<Object?> get props => [items];} class CartCubit extends Cubit<CartState> { CartCubit() : super(CartState([])); void add(Item item) { state.items.add(item); emit(state); }}Your
SessionBlocsubscribes to the auth stream in its constructor, but nothing reacts until the user opens Settings: why?// right: starts at once, for a bloc whose job begins before any widget asksBlocProvider( lazy: false, create: (c) => SessionBloc(c.read<AuthRepository>()), child: const App(),);…Your "Saved" snackbar shows up a second time when the user merely toggles a filter: what is wrong with that state?
// wrong: the effect is a plain field, so every later state still carries itclass FeedState extends Equatable { const FeedState({this.posts = const [], this.filter = Filter.all, this.message}); final List<Post> posts; final Filter filter; final String? message; // 'Saved' stays here until someone clears it…You call
bloc.add(SaveTapped())and readbloc.stateon the very next line to decide whether to pop: why is it always the previous state?In a debug build this handler fails with "emit was called after an event handler completed normally". What did it do wrong?
on<ProfileRequested>((event, emit) { emit(const ProfileLoading()); _repo.fetch(event.id).then((profile) { emit(ProfileLoaded(profile)); });});What happens when
LoggedInis added to this bloc, and why is the catch-all handler a bug?class AuthBloc extends Bloc<AuthEvent, AuthState> { AuthBloc(this._repo, this._analytics) : super(const AuthInitial()) { // "a spinner and a log line for every auth action" on<AuthEvent>((event, emit) async { emit(const AuthLoading()); await _analytics.log('$event'); }); on<LoggedIn>((event, emit) async { emit(Authenticated(await _repo.signIn(event.credentials))); }); on<LoggedOut>((event, emit) async { await _repo.signOut(); emit(const Unauthenticated()); }); }}How do you handle errors in a Bloc:
try/catch,addErrorandonError?class FeedBloc extends Bloc<FeedEvent, FeedState> { FeedBloc(this._api) : super(const FeedState.idle()) { on<FeedRequested>(_onRequested); } final FeedApi _api;…Your repository exposes a
Streamof updates. How do you feed it into a Bloc?// the emitter owns the subscriptionclass OrdersBloc extends Bloc<OrdersEvent, OrdersState> { OrdersBloc(this._repo) : super(const OrdersLoading()) { on<OrdersWatchStarted>( (event, emit) => emit.forEach<List<Order>>( _repo.watch(),…What is
BlocObserverand how do you use it for logging and crash reporting?class AppBlocObserver extends BlocObserver { const AppBlocObserver(this._crash); final CrashReporter _crash; @override void onTransition(Bloc bloc, Transition transition) {…How do you test a Bloc or Cubit with
bloc_testand a mocked repository?class MockAuthRepository extends Mock implements AuthRepository {} void main() { late MockAuthRepository repo; setUp(() => repo = MockAuthRepository());…How do you persist bloc state across app restarts with
hydrated_bloc?Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorageDirectory.web : HydratedStorageDirectory((await getApplicationSupportDirectory()).path),…How does BLoC compare with Riverpod and Provider with
ChangeNotifier, and which would you pick for a chat app?What are event transformers in BLoC, and what do
concurrent,sequential,droppableandrestartabledo?import 'package:bloc_concurrency/bloc_concurrency.dart';import 'package:rxdart/rxdart.dart'; // debounce first, then switchMap: a new query cancels the running oneEventTransformer<E> debounceRestartable<E>(Duration d) => (events, mapper) => events.debounceTime(d).switchMap(mapper);…Find the bug: tapping "Add" on two products in quick succession sometimes leaves only one of them in the cart.
class CartCubit extends Cubit<CartState> { CartCubit(this._api) : super(const CartState()); final CartApi _api; Future<void> add(Product product) async { final current = state; final saved = await _api.addToCart(product.id); emit(current.copyWith(items: [...current.items, saved])); }}How do you communicate between Blocs without coupling them?
// screen level: the widget coordinates, neither bloc knows the otherBlocListener<AuthBloc, AuthState>( listenWhen: (prev, curr) => prev is Authenticated && curr is Anonymous, listener: (context, _) => context.read<CartBloc>().add(const CartCleared()), child: const HomePage(),);…Pull-to-refresh blanks the list because the bloc emits
Loading. How would you model the state instead?enum FeedStatus { initial, loading, ready, failure } class FeedState extends Equatable { const FeedState({this.status = FeedStatus.initial, this.posts = const [], this.error}); final FeedStatus status;…After signing out and signing in as another account the profile screen still shows the first user's data: what is wrong with app-scoped blocs here?
MaterialApp( // above the Navigator, so pushed routes see the providers builder: (context, child) => BlocBuilder<AuthBloc, AuthState>( buildWhen: (p, c) => p.userId != c.userId, builder: (context, auth) => MultiBlocProvider( // new key per user: old providers unmount and close their blocs…