Irbisa · Cheatsheet
Modelling App State
Immutability, freezed, sealed unions, equality, optimistic updates, state restoration
What is the difference between ephemeral (local) state and app state in Flutter, and how do you decide where a value lives?
What does immutable state mean, and why do
CubitandNotifierexpect a new state object on every change?class CartState { const CartState({this.items = const []}); final List<Item> items; CartState copyWith({List<Item>? items}) => CartState(items: items ?? this.items);…How do you model loading, data and error state: separate booleans or a sealed class?
sealed class FeedState { const FeedState();} final class FeedLoading extends FeedState { const FeedLoading();…Find the bug: after offline caching shipped, users with a stale cache see an empty orders screen.
// OrdersState is sealed: OrdersLoading, OrdersLoaded, OrdersFailed, OrdersStale@overrideWidget build(BuildContext context) { return switch (context.watch<OrdersCubit>().state) { OrdersLoading() => const Center(child: CircularProgressIndicator()), OrdersLoaded(:final items) => OrderList(items), OrdersFailed(:final message) => ErrorView(message), _ => const SizedBox.shrink(), };}This widget sorts the list it gets from the cubit before building it. What goes wrong, and why did
finalnot stop it?class FeedState { const FeedState(this.items); final List<Item> items;} @overrideWidget build(BuildContext context) { final state = context.watch<FeedCubit>().state; state.items.sort((a, b) => b.date.compareTo(a.date)); return ListView( children: [for (final item in state.items) ItemTile(item)], );}How do you implement value equality for a state class, and why does it matter for rebuilds?
class FeedState { const FeedState({this.items = const [], this.loading = false}); final List<Item> items; final bool loading; @override…The cubit sets
onlyInStockon every tap of the switch, but the list stops reacting. What is wrong with this state class?class FilterState extends Equatable { const FilterState({this.query = '', this.onlyInStock = false}); final String query; final bool onlyInStock; FilterState copyWith({String? query, bool? onlyInStock}) => FilterState( query: query ?? this.query, onlyInStock: onlyInStock ?? this.onlyInStock, ); @override List<Object?> get props => [query];}Why do hand-written
copyWithmethods break down in nested state, and what doesfreezedgive you instead?// wrong: hand-written, three levels deep, and null cannot clear a fieldfinal next = state.copyWith( user: state.user.copyWith( address: state.user.address.copyWith(city: 'Almaty'), ),);…Find the bug: users who leave the profile screen while it is still loading trigger a crash report "Bad state: Cannot emit new states after calling close".
class ProfileCubit extends Cubit<ProfileState> { ProfileCubit(this._api) : super(ProfileLoading()); final ProfileApi _api; Future<void> load() async { final profile = await _api.fetch(); emit(ProfileLoaded(profile)); }}Why store
totalas a getter instead of a field, and what goes wrong when a screen copies state ininitState?When should related fields live in one state object instead of several separate notifiers?
// wrong: three notifiers, three notificationsvoid applyFilter(Filter f) { filter.value = f; // a listener here still sees page 4 page.value = 1; // and here the old selection selected.value = const {}; // only now consistent}…Why would you normalize state into a map keyed by id instead of nesting the same objects inside several lists?
class FeedState { const FeedState({this.posts = const {}, this.feed = const [], this.saved = const []}); final Map<PostId, Post> posts; // each post stored once final List<PostId> feed; // lists hold only ids final List<PostId> saved;…How do you model the state of a multi-step form, and when does the half-filled data become a real domain object?
class SignupDraft { String? email; String? name; // null while a required field is still missing Signup? toSignup() {…How do you implement an optimistic UI update and roll back when the request fails?
enum Sync { synced, pending, failed } class FeedCubit extends Cubit<List<Post>> { FeedCubit(this._api) : super(const []); final Api _api;…A user taps the star twice quickly. What can this cubit end up showing, and how do you make the result deterministic?
class FavoriteCubit extends Cubit<bool> { FavoriteCubit(this._api, bool initial) : super(initial); final Api _api; Future<void> toggle() async { final next = !state; await _api.setFavorite(next); emit(next); }}How do you decide how long a state holder lives (widget, route, session or app), and what goes wrong when it lives too long?
How does state restoration work in Flutter with
RestorationMixin, and what do you restore versus re-fetch after the OS kills the app?class _CheckoutPageState extends State<CheckoutPage> with RestorationMixin { final _note = RestorableTextEditingController(); final _orderId = RestorableStringN(null); @override String? get restorationId => 'checkout';…How would you model a checkout flow as a state machine, and what does a pure transition function buy you?
sealed class CheckoutState {}final class Idle extends CheckoutState {}final class Submitting extends CheckoutState {}final class AwaitingConfirmation extends CheckoutState { AwaitingConfirmation(this.paymentId); final String paymentId;…How do you version persisted state so that an app update cannot crash on data written by an older version?
const _version = 3; final _migrations = <int, Map<String, dynamic> Function(Map<String, dynamic>)>{ 1: (json) => {...json, 'plan': 'free'}, // v1 -> v2: new field 2: (json) => { // v2 -> v3: renamed field ...json,…