Flutter StatelessWidget vs StatefulWidget: In-Depth Understanding
Understanding the difference between StatelessWidget and StatefulWidget is fundamental to Flutter development.
This section will deeply analyze the usage scenarios and internal working mechanisms of these two types of Widgets.
Core difference
In Flutter, there are two main types of Widgets, and their fundamental difference lies in whether they can hold mutable state.
| Features | StatelessWidget | StatefulWidget |
|---|---|---|
| State | Immutable | mutable |
| Rebuild timing | Only when the parent Widget rebuilds | When state changes + parent Widget rebuilds |
| Performance | Higher (no need to listen to state) | Slightly lower (requires state management) |
| Use Case | Static content | Dynamic interactive content |
StatelessWidget Detailed Explanation
StatelessWidget is suitable for content that does not change over time. Once created, all its properties are final and cannot be modified.
Use Case
- Display static text, images, icons
- Displaying a data list (data source comes from the parent Widget)
- Layout containers without user interaction
- Read-only display sections in forms
Example: Complete StatelessWidget example
// User info display component (stateless)
class UserCard extends StatelessWidget {
// Properties are defined as final to ensure immutability
final String name;
final String email;
final String avatarUrl;
// const constructor improves performance
const UserCard({
super.key,
required this.name,
required this.email,
this.avatarUrl = '',
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// Avatar
CircleAvatar(
backgroundImage: avatarUrl.isNotEmpty
? NetworkImage(avatarUrl)
: null,
child: avatarUrl.isEmpty
? const Icon(Icons.person)
: null,
),
const SizedBox(width: 16),
// User info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
email,
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
],
),
),
);
}
}
StatefulWidget Detailed Explanation
StatefulWidget is suitable for scenarios that need to respond to user interactions or data changes. It consists of two parts: the Widget itself (immutable) and the State object (mutable).
Use Case
- Dynamic data such as counters, timers
- Form input
- Animation Control
- Network request status display
- User interaction response
State object lifecycle
| Methods | Description |
|---|---|
| initState | Called when the Widget is first created, used to initialize state. |
| didChangeDependencies | Called when the Widget's dependencies change. |
| build | Builds the UI, called every time the state changes. |
| didUpdateWidget | Called when the parent Widget rebuilds |
| dispose | Called when the Widget is removed, used to clean up resources. |
Example: Complete StatefulWidget example
// Counter app
class CounterApp extends StatefulWidget {
const CounterApp({super.key});
@override
State<CounterApp> createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
// Counter state
int _counter = 0;
// Timer (to demonstrate dispose)
Timer? _timer;
// 1. initState - Initialize state
@override
void initState() {
super.initState();
// Initialize counter
_counter = 0;
// Start a timer (increment every second)
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_increment();
});
}
// 2. didChangeDependencies - Called when dependencies change
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
// 3. build - Build UI
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Current count:',
style: TextStyle(fontSize: 20),
),
Text(
'$_counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
],
),
),
// Increment button
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
// Method to increment the counter
void _increment() {
// Must call setState to trigger UI update
setState(() {
_counter++;
});
}
// 4. didUpdateWidget - Called when parent widget rebuilds
@override
void didUpdateWidget(CounterApp oldWidget) {
super.didUpdateWidget(oldWidget);
}
// 5. dispose - Clean up resources
@override
void dispose() {
// Cancel timer to prevent memory leaks
_timer?.cancel();
super.dispose();
}
}
Correct usage of setState
In StatefulWidget, you must notify Flutter of state changes through setState.
Correct Usage
Example: Correct setState usage
void _increment() {
setState(() {
_counter++;
});
}
// Correct: When state update is unrelated to UI, you don't need setState
void _someInternalLogic() {
// Internal logic, does not affect UI
_calculateSomething();
}
// Correct: Update state after async operation
Future<void> _loadData() async {
final data = await fetchData();
setState(() {
_items = data;
});
}
Wrong usage
Example: Incorrect setState usage
void _increment() {
_counter++; // State has changed, but the UI won't update!
}
// Wrong: Calling setState in the build method
@override
Widget build(BuildContext context) {
// Never call setState in build!
setState(() {
_counter++; // This leads to an infinite loop
});
return Text('$_counter');
}
setState will trigger a re-invocation of the build method during the next frame rendering.
Frequent calls to setState or calling setState within the build method can both lead to performance issues.
How to choose
During development, you should choose the appropriate Widget type based on actual needs:
Choose StatelessWidget when
- The Widget's appearance does not depend on any mutable state.
- All required data is passed in from the parent Widget via the constructor.
- The Widget does not need to respond to user interactions.
- Pursuing optimal performance
Choose StatefulWidget when
- The Widget needs to maintain internal state.
- Needs to respond to user input (clicks, swipes, etc.)
- Needs to respond to data changes (e.g., network request responses)
- Need to control animations