Flutter Common Mistakes¶
This section describes common mistakes when using SimpleAppState with Flutter.
All examples below may compile, but they violate the design philosophy and may lead to unpredictable rebuilds or hard-to-maintain code.
Storing state in widgets¶
Mistake:
Storing application state inside a StatefulWidget.
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int count = 0; // ❌ application state
@override
Widget build(BuildContext context) {
return Text('$count');
}
}
Why this is a problem
the state is tied to the widget lifecycle
it is lost when the widget is rebuilt or replaced
it cannot be shared or persisted easily
Correct approach
Application state should live in SimpleAppState. Widgets should only read state and rebuild when it changes.
Mutating values returned by get()¶
Mistake:
Modifying an object returned by StateSlot.get().
final list = itemsSlot.get();
list.add('new item'); // ❌ no state update
Why this is a problem
get() always returns a deep copy
mutating it does not affect the actual state
no listeners are notified
Correct approach
Always update state via set or update:
itemsSlot.update((oldCopy) {
oldCopy.add('new item');
return oldCopy;
});
Calling set() during build¶
Mistake:
Updating state during build() in a way that changes the value.
@override
Widget build(BuildContext context) {
countSlot.update((v) => v + 1); // ❌
return const Text('Hello');
}
or:
@override
Widget build(BuildContext context) {
final list = itemsSlot.get();
list.add('new item');
itemsSlot.set(list); // ❌
return const Text('Hello');
}
Why this is a problem
build() may be called many times by Flutter
updates that actually change values trigger notifications
this can cause repeated rebuilds and unstable behavior
SimpleAppState does guard against no-op updates:
if (oldValue == value) return;
This means that calling set() with the same value does not trigger notifications.
However:
update() almost always produces a new value
mutating a value obtained from get() and setting it back also produces a change
These cases are not guarded and will trigger rebuilds.
Correct approach
State updates should be performed in response to:
user interactions (button presses)
lifecycle callbacks (initState, didChangeDependencies)
external events (timers, streams, async results)
Never perform value-changing updates inside build().
Key takeaway
Calling set() with the same value is harmless but useless
Any update that changes state during build() is unsafe
build() must remain a pure function of current state
Reading state without declaring dependencies¶
Mistake:
Reading a slot value without using SlotStatefulWidget.
Widget build(BuildContext context) {
final count = countSlot.get(); // ❌
return Text('$count');
}
Why this is a problem
the widget is not registered as a listener
it will not rebuild when the slot changes
UI becomes stale
Correct approach
Use StateSlotBuilder or SlotStatefulWidget to declare dependencies explicitly.
Using widget identity as state identity¶
Mistake:
Creating slots dynamically based on widget instances.
final slot = state.slot<int>('${widget.hashCode}', initial:0); // ❌
Why this is a problem
slot identity becomes unstable
restored or persisted state cannot be reused
undo / redo breaks
Correct approach
State slots must have stable, global identities. Declare all slots in a fixed location (for example app_state.dart).
Expecting SimpleAppState to control rebuilds¶
Mistake:
Assuming SimpleAppState decides when widgets rebuild.
Why this is a problem
Flutter owns the widget tree
rebuild timing is a Flutter responsibility
SimpleAppState only requests rebuilds
Correct approach
Think in layers:
SimpleAppState owns data
StateSlot defines identity
Flutter decides rebuild timing and rendering
Overusing batch()¶
Mistake:
Wrapping every update in batch().
state.batch(() {
countSlot.set(1);
});
Why this is unnecessary
single updates already notify efficiently
excessive batching reduces clarity
batch is intended for grouped changes
Correct approach
Use batch() only when:
multiple slots are updated together
you want a single rebuild after all changes
Make heavy use of StateSlotBuilder¶
Mistake:
Use StateSlotBuilder for no reason.
Why this is a problem
Using StateSlotBuilder can make it difficult to understand dependencies in your code.
Correct approach
Use StateSlotBuilder only when absolutely necessary, such as when you want to extend existing code.
For a detailed explanation, see:
Summary¶
Most mistakes come from mixing responsibilities.
Remember these simple rules:
SimpleAppState owns application state
StateSlot defines what the state is
widgets declare dependencies and rebuild
Flutter controls the widget tree
Keeping these boundaries clear leads to predictable behavior and maintainable code.