Irbisa · Шпаргалка
Состояние на BLoC
Паттерн BLoC, Cubit, Events, States, BlocBuilder, BlocConsumer
Что такое паттерн BLoC и чем
Blocотличается отCubit?// Cubit: input is a method callclass CounterCubit extends Cubit<int> { CounterCubit() : super(0); void increment() => emit(state + 1);}…Что делают
BlocBuilder,BlocListener,BlocConsumerиBlocSelector, и зачем нужныbuildWhenиlistenWhen?// 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}')),);…Найдите баг: ввод в поле поиска перестраивает список открытых задач, хотя
state.todosостаётся тем же списком.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]), ), ); }}Чем
BlocProvider(create:)отличается отBlocProvider.valueи как передать существующий блок на новый маршрут?// owner: builds the bloc and closes it when this subtree goes awayBlocProvider( create: (context) => CartBloc(context.read<CartRepository>()), child: const CartPage(),);…Зачем BLoC нужно равенство по значению (
Equatable,freezed) для состояний и что ломается без него?// before: no value equality, identity decidesfinal class ProfileLoaded extends ProfileState { ProfileLoaded(this.user); final User user;}// emit(ProfileLoaded(u)); emit(ProfileLoaded(u));…Этот Cubit добавляет товар, но после первого товара корзина на экране перестаёт обновляться. Почему?
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); }}SessionBlocподписывается на поток авторизации в конструкторе, но приложение не реагирует, пока пользователь не откроет Settings: почему?// 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(),);…Снекбар «Сохранено» появляется второй раз, когда пользователь просто переключает фильтр: что не так с состоянием?
// 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…Вы вызываете
bloc.add(SaveTapped())и на следующей же строке читаетеbloc.state, чтобы решить, закрывать ли экран: почему там всегда старое состояние?В debug-сборке этот обработчик падает с «emit was called after an event handler completed normally». Что он сделал не так?
on<ProfileRequested>((event, emit) { emit(const ProfileLoading()); _repo.fetch(event.id).then((profile) { emit(ProfileLoaded(profile)); });});Что произойдёт, когда в этот блок добавят
LoggedIn, и почему общий обработчик считается багом?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()); }); }}Как обрабатывать ошибки в Bloc:
try/catch,addErrorиonError?class FeedBloc extends Bloc<FeedEvent, FeedState> { FeedBloc(this._api) : super(const FeedState.idle()) { on<FeedRequested>(_onRequested); } final FeedApi _api;…Репозиторий отдаёт
Streamобновлений. Как завести его в 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(),…Что такое
BlocObserverи как использовать его для логирования и отчётов о падениях?class AppBlocObserver extends BlocObserver { const AppBlocObserver(this._crash); final CrashReporter _crash; @override void onTransition(Bloc bloc, Transition transition) {…Как тестировать Bloc или Cubit через
bloc_testс замоканным репозиторием?class MockAuthRepository extends Mock implements AuthRepository {} void main() { late MockAuthRepository repo; setUp(() => repo = MockAuthRepository());…Как сохранить состояние блока между перезапусками приложения через
hydrated_bloc?Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb ? HydratedStorageDirectory.web : HydratedStorageDirectory((await getApplicationSupportDirectory()).path),…Как BLoC соотносится с Riverpod и Provider с
ChangeNotifier, и что бы вы выбрали для чата?Что такое трансформеры событий в BLoC и что делают
concurrent,sequential,droppableиrestartable?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);…Найдите баг: если быстро нажать «Добавить» на двух товарах подряд, в корзине иногда оказывается только один из них.
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])); }}Как организовать коммуникацию между Bloc-ами без жёсткой связи?
// 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 очищает список, потому что bloc выпускает
Loading. Как смоделировать состояние иначе?enum FeedStatus { initial, loading, ready, failure } class FeedState extends Equatable { const FeedState({this.status = FeedStatus.initial, this.posts = const [], this.error}); final FeedStatus status;…После выхода и входа под другим аккаунтом экран профиля всё ещё показывает данные первого пользователя: что не так с блоками уровня приложения?
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…