What is a StatefulWidget?
A StatefulWidget is a widget that can change over time. Unlike StatelessWidget which is a snapshot, StatefulWidget is alive — it reacts to user interactions, data changes, and events. Every time you tap a button, fill a form, or load new data, you're working with stateful widgets.
Here's the interesting part: a StatefulWidget has two classes. The widget itself is immutable (it doesn't change), but it creates a separate State object that holds the mutable data and survives rebuilds. This separation is the key to understanding Flutter's state management.
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $_count', style: TextStyle(fontSize: 24)),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: Text('Increment'),
),
],
);
}
}
Try it Yourself →
How setState Works
When you call setState(), you're telling Flutter: "Hey, something changed — rebuild this widget." Flutter then calls the build method again with the updated data. The UI refreshes to reflect the new state. It's that simple.
class ToggleButton extends StatefulWidget {
const ToggleButton({Key? key}) : super(key: key);
@override
_ToggleButtonState createState() => _ToggleButtonState();
}
class _ToggleButtonState extends State<ToggleButton> {
bool _isOn = false;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
setState(() {
_isOn = !_isOn; // Toggle the state
});
},
style: ElevatedButton.styleFrom(
backgroundColor: _isOn ? Colors.green : Colors.red,
),
child: Text(_isOn ? 'ON' : 'OFF'),
);
}
}
Think of it like a light switch — the button stays the same, but the state (on/off) changes, and the UI updates to show the new state. This pattern is fundamental to Flutter development.
Try it Yourself →When to Use StatefulWidget
StatefulWidget is for anything interactive: forms, toggles, animations, data that loads from an API, scroll positions, text field inputs. If your widget needs to update based on user interaction or data changes, it needs to be stateful.
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
String? _errorMessage;
Future<void> _login() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
// Simulate API call
await Future.delayed(Duration(seconds: 2));
if (_emailController.text.isEmpty) {
throw Exception('Email is required');
}
// Success — navigate or do something
} catch (e) {
setState(() {
_errorMessage = e.toString();
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
),
if (_errorMessage != null)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(_errorMessage!, style: TextStyle(color: Colors.red)),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _isLoading ? null : _login,
child: _isLoading
? SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2))
: Text('Login'),
),
],
);
}
}
Try it Yourself →
The Pattern to Remember
Here's the pattern for every StatefulWidget: create a class extending StatefulWidget, create a separate State class, call setState() whenever data changes. That's it. Don't put mutable data in StatelessWidget — it won't update. The State object survives widget rebuilds, so your data persists even when the UI redraws.
// The StatefulWidget pattern
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// 1. Mutable state lives here
var _data = 'initial';
// 2. Build method creates the UI
@override
Widget build(BuildContext context) {
return Text(_data);
}
// 3. setState() updates the UI
void _updateData() {
setState(() {
_data = 'updated';
});
}
}
Master this pattern and you've mastered the core of Flutter state management. Everything else builds on this foundation.
Try it Yourself →