Irbisa · Cheatsheet

Basic State Management

setState, InheritedWidget, ValueNotifier

Junior Developer · 23 questions

  1. 01

    Why do you need a state-management approach at all when setState exists?

    Because state that several widgets read, or that outlives a screen, does not fit in one State: you end up passing values and callbacks through every constructor, logic stays untestable inside widgets, and rebuilds are too broad.

  2. 02

    Which state-management approaches do you know in Flutter, and how do you choose one for a project?

    Built in: setState, InheritedWidget, ValueNotifier/ChangeNotifier; packages: Provider, Riverpod, BLoC/Cubit, rarely MobX, Redux, GetX.

  3. 03

    How does setState() work, what happens when you call it, and why must you not call it inside build()?

    setState runs your closure right away, then marks this State's element dirty and schedules a frame.

    class Counter extends StatefulWidget {  const Counter({super.key});   @override  State<Counter> createState() => _CounterState();}…
  4. 04

    Tapping this button calls setState, yet the counter always shows 0. Why, and what else makes setState seem to do nothing?

    class _CounterState extends State<Counter> {  @override  Widget build(BuildContext context) {    int count = 0;    return TextButton(      onPressed: () => setState(() => count++),      child: Text('$count'),    );  }}

    count is a local variable of build.

  5. 05

    What does it mean to "lift state up" in Flutter, and when should you do it?

    Lifting state up means moving a value to the lowest common parent of the widgets that read or change it, then passing the value down as a parameter and changes back up through callbacks.

    class FilterBar extends StatefulWidget {  const FilterBar({super.key});  @override  State<FilterBar> createState() => _FilterBarState();}…
  6. 06

    Why must the callback you pass to setState be synchronous, and what happens if you make it async?

    setState runs your closure immediately and then marks the element dirty.

    // wrong: debug throws "setState() callback argument returned a Future"void _loadWrong() {  setState(() async {    _user = await api.fetchUser(); // assigned after the rebuild already ran  });}…
  7. 07

    What has to be released in a State's dispose(), and what goes wrong when you forget?

    Everything the State created that outlives one build: controllers, subscriptions, timers and listeners keep running or hold native resources, and the ones with callbacks keep the State alive.

    class _EditorState extends State<Editor> with SingleTickerProviderStateMixin {  final _text = TextEditingController();  final _focus = FocusNode();  late final AnimationController _anim;  StreamSubscription<Draft>? _sub;  Timer? _autosave;…
  8. 08

    What does this code print when the button is tapped once (not counting the initial build: 0), and how many times does build run?

    class _DemoState extends State<Demo> {  int _count = 0;   void _tap() {    setState(() => _count++);    setState(() => _count++);    setState(() => _count++);    print('after: $_count');  }   @override  Widget build(BuildContext context) {    print('build: $_count');    return TextButton(onPressed: _tap, child: Text('$_count'));  }}

    It prints after: 3 first and then build: 3 once.

  9. 09

    A user double-taps Save and two orders get created. How do you stop the second tap with local state?

    One bool _saving on the State: set it before the await, clear it in a finally when the screen stays open, and pass onPressed: null while it is set, so the button is visibly disabled during the work.

    class _SaveButtonState extends State<SaveButton> {  bool _saving = false;   Future<void> _save() async {    if (_saving) return; // the second tap lands here    setState(() => _saving = true); // same synchronous slice: no race…
  10. 10

    What happens if you call setState after dispose(), and why do you check mounted after every await?

    It throws: in debug "setState() called after dispose()", in release a null-check error.

    class _ProfilePageState extends State<ProfilePage> {  Profile? _profile;   Future<void> _refresh() async {    final messenger = ScaffoldMessenger.of(context); // capture before the gap    try {…
  11. 11

    What is the difference between initState and didChangeDependencies?

    Both run before the first build, but initState runs exactly once, while didChangeDependencies runs right after it and again every time an inherited widget this State depends on changes.

    class _PostFeedState extends State<PostFeed> {  FeedRepository? _repo;  StreamSubscription<List<Post>>? _sub;  List<Post> _posts = const [];   @override…
  12. 12

    Find the bug: the parent resets the cart line to 1, but this quantity stepper keeps showing the old number.

    class _QuantityStepperState extends State<QuantityStepper> {  late int _qty;  @override  void initState() {    super.initState();    _qty = widget.quantity;  }   void _inc() {    setState(() => _qty++);    widget.onChanged(_qty);  }  @override  Widget build(BuildContext context) =>      TextButton(onPressed: _inc, child: Text('$_qty'));}

    _qty is copied from widget.quantity only in initState.

  13. 13

    What causes the "setState() or markNeedsBuild() called during build" error, and how do you fix it?

    Flutter is already walking the tree when build runs, and marking an ancestor or a widget that was already built as dirty in that window would leave the frame half built, so it refuses.

    // wrong: the parent's setState runs while the tree is being builtclass _ItemsPanelState extends State<ItemsPanel> {  @override  Widget build(BuildContext context) {    if (widget.items.isEmpty) widget.onEmpty(); // parent callback calls its setState    return ItemList(widget.items);…
  14. 14

    A checkbox inside a dialog never updates when you call setState. Why, and what is the fix?

    The dialog is a separate route: your page's setState rebuilds the page, while the dialog's content was built by the builder you handed to showDialog and is not part of the page's subtree.

    final accepted = await showDialog<bool>(  context: context,  builder: (dialogContext) {    var agreed = false; // lives as long as this builder's closure    return StatefulBuilder(      builder: (context, setLocalState) => AlertDialog(…
  15. 15

    What is ValueNotifier, how does it differ from ChangeNotifier, and when do you prefer it over setState?

    ValueNotifier<T> is a ChangeNotifier that holds one value and notifies its listeners when that value is replaced.

    class _FavoriteButtonState extends State<FavoriteButton> {  final _liked = ValueNotifier<bool>(false);   @override  void dispose() {    _liked.dispose();…
  16. 16

    An expensive chart inside a ValueListenableBuilder rebuilds on every tick. What is the builder's child parameter for?

    child is built once, outside the builder, and passed back into it unchanged.

    // wrong: a fresh ExpensiveChart instance on every tick, so it rebuildsValueListenableBuilder<int>(  valueListenable: _ticks,  builder: (context, ticks, _) => Column(children: [    Text('$ticks'),    ExpensiveChart(series: widget.series),…
  17. 17

    A form rebuilds the whole page on every keystroke because onChanged calls setState. Where should that text actually live?

    In the TextEditingController.

    // wrong: every keystroke rebuilds the page, and _text duplicates the truthTextField(onChanged: (v) => setState(() => _text = v)); // right: the controller is the source of truth; only the button listensclass _ComposerState extends State<Composer> {  final _controller = TextEditingController();…
  18. 18

    What problem does InheritedWidget solve, and how does updateShouldNotify work?

    InheritedWidget hands data to everything below it without passing it through every constructor, and rebuilds only the widgets that read it.

    class AppColors extends InheritedWidget {  const AppColors({super.key, required this.primary, required super.child});   final Color primary;   static AppColors of(BuildContext context) {…
  19. 19

    How is InheritedWidget different from Provider?

    Provider is built on InheritedWidget.

  20. 20

    What are the main criticisms of GetX, and why do many teams avoid it?

    GetX bundles state, navigation, DI, translations and HTTP into one package built on global singletons and context-free APIs.

  21. 21

    Find the bug in how this CartBadge subscribes to the cart, and fix it.

    class _CartBadgeState extends State<CartBadge> {  @override  void initState() {    super.initState();    widget.cart.addListener(() => setState(() {}));  }   @override  void dispose() {    widget.cart.removeListener(() => setState(() {}));    super.dispose();  }   @override  Widget build(BuildContext context) => Text('${widget.cart.count}');}

    removeListener gets a new closure that was never added, so it removes nothing.

  22. 22

    What is the difference between dependOnInheritedWidgetOfExactType and getInheritedWidgetOfExactType, and why does a reader sometimes never rebuild?

    Three links have to hold: the inherited widget must be a new instance with a new value, its updateShouldNotify must return true, and the reader must actually have registered a dependency.

    class CartScope extends InheritedWidget {  const CartScope({super.key, required this.items, required super.child});  final List<Item> items;   // registers this context as a dependent  static CartScope of(BuildContext context) =>…
  23. 23

    Every widget that reads your InheritedWidget rebuilds when any single field changes. How do you make a reader depend on one field only?

    Use InheritedModel: a dependent names an aspect when it reads, and the framework asks per dependent whether the aspects that widget cares about actually changed.

    enum PrefAspect { currency, locale } class Prefs extends InheritedModel<PrefAspect> {  const Prefs(      {super.key, required this.currency, required this.locale, required super.child});…