A widget is an immutable description of part of the UI. Flutter turns the widget tree into a longer-lived element tree and a render tree; when you call setState, only the widgets that describe the changed subtree are rebuilt and the elements decide what actually needs to be updated on screen.
- Widgets are immutable configuration objects; a new widget instance is cheap and is created on every build.
- StatelessWidget: build() depends only on its constructor arguments and BuildContext.
- StatefulWidget creates a State object that survives rebuilds and holds mutable data.
- State lifecycle: initState → didChangeDependencies → build → (didUpdateWidget / setState → build)* → deactivate → dispose.
- Three trees: widgets (what you write), elements (the persistent instances that hold state), render objects (layout and painting).
- setState schedules a rebuild of that State's subtree; it does not rebuild the whole app and it must not be called after dispose.
Blueprints, construction crew, and the building
A widget is a blueprint: a cheap, immutable sheet of paper that says what should exist. The element tree is the construction crew that reads blueprints and keeps track of what has already been built; when you hand the crew a new blueprint for the same spot, they compare it with the previous one and only touch what differs. The render tree is the physical building: walls (layout) and paint (rendering). State objects are the crew's notebooks: they persist between blueprint revisions, which is why a counter keeps its value when the page is rebuilt.
Minimal working example
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(child: Text('Count: $_count')),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
}- L2Widgets are immutable; const constructors let Flutter skip rebuilding identical subtrees.
- L6createState is called once per element; the returned State persists across rebuilds.
- L10Mutable data lives in State, never in the widget.
- L13setState marks this element dirty; build runs again before the next frame.
- L19build must be pure: no side effects, no setState calls inside it.
Use cases
- Use a StatelessWidget whenever the UI is a pure function of its inputs (most leaf and layout widgets).
- Use a StatefulWidget for local, ephemeral state: a toggle, a text field's focus, an animation controller, a page index.
- Use lifecycle hooks (initState/dispose) to own resources that must be created once and released: controllers, subscriptions, focus nodes.
Benefits
- Immutable widgets make rebuilds cheap and predictable; Flutter can diff and reuse elements efficiently.
- Local state stays close to the UI that uses it, which keeps small components self-contained.
Limitations
- State in a StatefulWidget is lost when the element is removed from the tree (navigating away, conditional rendering, changing Keys).
- Sharing a StatefulWidget's state with distant widgets requires lifting it up or an app-state solution; see the state management topic.
- Calling setState in build or after dispose is an error.
Alternatives and trade-offs
- InheritedWidget / app-state packages — Several screens need the same data (user session, cart, settings).
- ValueListenableBuilder / ListenableBuilder — You want to rebuild a small subtree in response to a Listenable without a StatefulWidget around it.
- StatelessWidget + callbacks — The parent already owns the state; pass values down and events up.
Doing work in build()
Problem: build can run many times per second. Creating controllers, starting network calls or subscribing to streams inside build leaks resources and repeats work.
Fix: Create long-lived objects in initState and release them in dispose. Keep build a pure description of the UI.
@override
Widget build(BuildContext context) {
final controller = TextEditingController(); // recreated every build
return TextField(controller: controller);
}late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => TextField(controller: _controller);Calling setState after the widget was disposed
Problem: An async callback (HTTP response, timer, stream) completes after the user navigated away, and setState throws 'setState() called after dispose()'.
Fix: Check `mounted` before calling setState in async callbacks, and cancel timers and subscriptions in dispose.
Future<void> _load() async {
final data = await fetchData();
setState(() => _data = data); // may run after dispose
}Future<void> _load() async {
final data = await fetchData();
if (!mounted) return;
setState(() => _data = data);
}Reordering stateful children without Keys
Problem: Elements are matched by runtimeType and position. When a list of stateful items is reordered, each State stays at its old position and shows the wrong data.
Fix: Give each child a stable Key (for example ValueKey(item.id)) so the element diffing algorithm moves the State with the item.
Mutating state without setState
Problem: Changing a field directly updates the value but never schedules a rebuild, so the UI shows stale data until something else triggers a build.
Fix: Wrap every state mutation that should be visible in setState(() { ... }).
This page is Boring Doc’s independent explanation. For the complete specification use the official pages below (content © The Flutter Authors, CC BY 3.0; code samples BSD). Dates show when the explanation was last checked and when the official page last changed.
- https://docs.flutter.dev/resources/architectural-overview
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/learn/pathway/tutorial/widget-fundamentals
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/learn/pathway/tutorial/stateful-widget
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- State class API reference Flutter 3.47.7https://api.flutter.dev/flutter/widgets/State-class.html
- Last checked
- Oct 21, 2026
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.