Basic State Management
setState, InheritedWidget, ValueNotifier
- 01
How does setState work and what are its performance implications?
EasysetState()marks the State as dirty and schedules rebuild ofbuild()for the next frame.class CartPage extends StatefulWidget { const CartPage({super.key}); @override State<CartPage> createState() => _CartPageState(); } class _CartPageState extends State<CartPage> { … - 02
What is InheritedWidget and how does it work?
MediumInheritedWidget is the fundamental mechanism for efficient state propagation down the widget tree.
// Define InheritedWidget class AppTheme extends InheritedWidget { final Color primary; final double fontSize; const AppTheme({ … - 03
When should you use StatelessWidget vs StatefulWidget?
EasyDefault to StatelessWidget.
// ✅ Stateless — pure props in class PriceTag extends StatelessWidget { final int cents; const PriceTag(this.cents, {super.key}); @override Widget build(BuildContext context) => Text('\$${(cents / 100).toStringAsFixed(2)}'); … - 04
What is ValueNotifier and when should you prefer it over setState?
MediumValueNotifier<T>is a tiny ChangeNotifier with a single value.class FavoriteButton extends StatefulWidget { const FavoriteButton({super.key}); @override State<FavoriteButton> createState() => _FavoriteButtonState(); } class _FavoriteButtonState extends State<FavoriteButton> { … - 05
What does it mean to "lift state up" in Flutter?
EasyLifting state up means moving a piece of state to the lowest common ancestor of the widgets that read or write it, then passing it down via constructor parameters and passing change callbacks back up.
// ❌ Before — each tile owns its selection, parent cannot know which is selected class _Tile extends StatefulWidget { final String label; const _Tile(this.label); @override State<_Tile> createState() => _TileState(); } … - 06
What is the mounted check and why is it required after an async gap?
MediumAcross an
await, the widget owning the State may have been disposed.class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @override State<ProfilePage> createState() => _ProfilePageState(); } class _ProfilePageState extends State<ProfilePage> { … - 07
What is the difference between initState and didChangeDependencies?
MediumBoth run before the first build, but they answer different questions.
class _PostFeedState extends State<PostFeed> with SingleTickerProviderStateMixin { // supplies the vsync below late final AnimationController _shimmer; // controller owns lifetime StreamSubscription? _sub; List<Post> _posts = const []; … - 08
What is InheritedNotifier and when does it beat a plain InheritedWidget?
MediumInheritedNotifier<T extends Listenable>is a built-in InheritedWidget that wraps a Listenable (typically ChangeNotifier or ValueNotifier).// 1) The notifier holds the state class CounterModel extends ChangeNotifier { int _value = 0; int get value => _value; void inc() { _value++; notifyListeners(); } } … - 09
What problem do widget Keys solve, and when do you need GlobalKey vs ValueKey?
HardKeys preserve State across rebuilds when widgets get reordered or swapped.
// ❌ Without keys — toggling Sort reorders tiles, State (e.g. selection) follows the slot, not the item List<Widget> tilesNoKeys(List<Person> people) => [for (final p in people) PersonTile(p)]; // ✅ ValueKey by id — State sticks to the right item across reorder List<Widget> tilesKeyed(List<Person> people) => … - 10
What causes the "setState() or markNeedsBuild() called during build" error, and how do you fix it?
MediumFlutter is in the middle of walking the tree while build runs, so marking anything dirty inside that window would leave the frame half built.
// ❌ setState() or markNeedsBuild() called during build @override Widget build(BuildContext context) { if (widget.items.isEmpty) { setState(() => _showEmpty = true); // dirty during build } … - 11
A checkbox inside a dialog never updates when you call setState. Why, and what is the fix?
MediumThe dialog lives on its own route with its own context, so the page's setState rebuilds the page and never touches the dialog's content.
// ❌ the outer setState rebuilds the page, not the dialog showDialog( context: context, builder: (_) => AlertDialog( content: Checkbox( value: _agreed, … - 12
What has to be released in a State's dispose(), and what goes wrong when you forget?
EasyAnything the State created that outlives a single build has to be released there, because it holds a reference back to the State.
class _EditorState extends State<Editor> with SingleTickerProviderStateMixin { final _text = TextEditingController(); final _scroll = ScrollController(); final _focus = FocusNode(); late final _anim = AnimationController( vsync: this, … - 13
Why does the callback you hand to setState have to be synchronous, and what happens if you make it async?
EasysetStateruns the callback immediately and synchronously and then marks the element dirty, so anasynccallback returns a Future the framework throws away and your fields are assigned long after the rebuild they were supposed to cause.// ❌ debug: "setState() callback argument returned a Future" void _load() { setState(() async { // Future<void> Function() slips _user = await api.fetchUser(); // into a VoidCallback slot }); // dirty now, assigned much later } … - 14
A user double-taps Save, two orders get created — how do you stop the second tap with local state?
EasyOne in-flight boolean on the State, set before the await and cleared in a
finally, and the same field drivesonPressed: nullso the button is visibly dead while the work runs.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 … - 15
A parent passes a new title down, but the child still shows the old one — why doesn't initState run again?
MediumThe State object is reused across parent rebuilds — Flutter swaps only the
widgetfield on the same State — so anything you copied out ofwidgetininitStateis a snapshot that nothing ever refreshes.// ❌ snapshots taken once — the parent's new values never arrive class _EditorState extends State<Editor> { late String _title = widget.title; // stale forever late final _text = TextEditingController(text: widget.body); } … - 16
Your custom InheritedWidget holds the new value but nothing under it rebuilds — where do you look first?
MediumDependents rebuild only when the
InheritedElementis updated with a new widget instance whoseupdateShouldNotifyreturns true, and only for contexts that actually registered a dependency — so one of those three links is broken.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) => … - 17
A form rebuilds the whole page on every keystroke because onChanged calls setState — where should that text actually live?
MediumIn the
TextEditingController: it is aValueNotifier<TextEditingValue>that theTextFieldalready listens to, so the field repaints itself on every character and nothing above it has to rebuild at all.// ❌ every keystroke rebuilds the whole page, and _text duplicates the truth TextField(onChanged: (v) => setState(() => _text = v)); // ✅ the controller is the source of truth; only the button listens to it class _ComposerState extends State<Composer> { final _controller = TextEditingController(); … - 18
An expensive chart inside a ValueListenableBuilder rebuilds on every tick — what is the builder's child parameter for?
Mediumchildis built once, outside the builder, and handed back to it unchanged — and because the widget instance is identical between rebuilds, the element short-circuits and that subtree is never rebuilt.// ❌ a fresh ExpensiveChart instance on every tick → the subtree rebuilds ValueListenableBuilder<int>( valueListenable: _ticks, builder: (context, ticks, _) => Column(children: [ Text('$ticks'), ExpensiveChart(series: widget.series), // rebuilt 60x a second for nothing … - 19
Everything under your InheritedWidget rebuilds when any single field changes — how do you make a widget depend on one field only?
HardUse
InheritedModel<T>: dependents declare an aspect when they read, andupdateShouldNotifyDependentis asked per dependent whether the aspects that particular widget cares about actually changed.enum PrefAspect { currency, locale } class Prefs extends InheritedModel<PrefAspect> { const Prefs({ super.key, required this.currency, … - 20
Android kills your backgrounded app and the user comes back to an empty form on the first screen — what does Flutter offer for that?
HardRestorationMixinwith restorable properties: Flutter serializes a bucket of UI state that the engine hands back after an OS-initiated kill, so the user returns to the same route, scroll offset and half-typed form.void main() => runApp(const MaterialApp( restorationScopeId: 'app', // without a scope, nothing below restores home: SurveyPage(), )); class _SurveyPageState extends State<SurveyPage> with RestorationMixin { …