Flutter provides developers with two main types of widgets: Stateless Widgets and Stateful Widgets. While Stateless Widgets are perfect for static UI that never changes, most real-world applications need dynamic and interactive UI that responds to user actions, data updates, and animations.
That’s where Stateful Widgets come in.
In this article, we will go deep into an example of a Stateful Widget in Flutter—the CounterApp—and break down every detail about how it works, its lifecycle, why we use it, and best practices.
Introduction to Stateful Widgets
A Stateful Widget is a widget that:
- Can store mutable state (data that can change over time).
- Automatically rebuilds the UI when its state changes.
- Uses the
setState()method to notify Flutter that the widget tree needs to be updated.
In contrast to Stateless Widgets, Stateful Widgets are dynamic and are perfect for handling interactions such as:
- Button clicks
- Form inputs
- Page navigation
- Animations
- Real-time data updates
Structure of a Stateful Widget
A Stateful Widget consists of two classes:
- The Widget Class
- Extends
StatefulWidget. - Immutable itself, but linked to a State object.
- Defines the
createState()method, which returns the State object.
- Extends
- The State Class
- Extends
State<T>whereTis the Stateful Widget. - Holds the mutable state.
- Contains the
build()method to define the UI. - Uses
setState()to trigger UI rebuilds when state changes.
- Extends
Example: CounterApp in Flutter
Let’s look at a simple but powerful example of a Stateful Widget—a counter app that increases the count when a button is pressed.
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("Count: $count"),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text("Increment"),
)
],
);
}
}
Breaking Down the Code
1. Declaring the Stateful Widget
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
CounterAppextendsStatefulWidget.- This class is immutable, just like a Stateless Widget.
- But unlike Stateless Widgets, it is connected to a State object that stores the actual data (
count). - The
createState()method returns_CounterAppState, which manages the widget’s state.
2. Declaring the State Class
class _CounterAppState extends State<CounterApp> {
int count = 0;
_CounterAppStateextendsState<CounterApp>.- This is where the mutable state (
count) is declared. int count = 0;initializes the counter at zero.
3. The build() Method
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("Count: $count"),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text("Increment"),
)
],
);
}
- The
build()method is called whenever the widget is created or rebuilt. - It returns a UI tree that shows:
- A
Textwidget displaying the current value ofcount. - An
ElevatedButtonthat increments the counter when pressed.
- A
4. The setState() Method
onPressed: () => setState(() => count++),
setState()is crucial in Stateful Widgets.- It notifies Flutter that the state (
count) has changed. - Flutter then rebuilds the widget tree to update the UI.
- Without
setState(), the UI would not refresh.
Lifecycle of CounterApp
To fully understand this example, let’s map out the lifecycle of the Stateful Widget:
- Constructor Called –
CounterAppis created. createState()Called – LinksCounterAppwith_CounterAppState.initState()(optional) – Runs once before build (not used here).build()Called – DisplaysCount: 0and the button.- User Presses Button – Triggers
setState(). - State Updates –
countincrements (count++). build()Recalled – UI rebuilt showing updated count.- Widget Removed – If the CounterApp is removed,
dispose()is called (if implemented).
How It Works in Practice
Imagine running the app:
- At first, the screen shows:
Count: 0 [Increment Button] - When you press Increment, the
onPressedcallback runs:setState(() => count++);incrementscountby 1.- Flutter calls
build()again.
- Now the UI updates to:
Count: 1 [Increment Button] - Each button press increases the count and triggers a rebuild.
Why Use Stateful Widgets?
Stateful Widgets are essential because they allow Flutter apps to:
- Respond to user interactions.
- Manage dynamic data.
- Handle real-time updates from APIs.
- Support animations and transitions.
In short: If your UI changes, you need a Stateful Widget.
Real-World Use Cases of Stateful Widgets
The CounterApp is a simple demo, but Stateful Widgets are used everywhere:
- Forms – Capturing user input like name, email, and password.
- Shopping Carts – Adding/removing items dynamically.
- Music Players – Play, pause, skip, and update progress bar.
- Social Media Apps – Liking posts, adding comments, refreshing feeds.
- Games – Updating scores, player positions, and animations.
- Data Fetching – Displaying loading spinners while fetching API data.
Advantages of Stateful Widgets
- Dynamic UI – Perfect for apps where the UI changes.
- Easy State Management – Built-in
setState()makes updates straightforward. - Encapsulation – Each Stateful Widget manages its own state.
- Flexibility – Works for both small and large interactive components.
Limitations of Stateful Widgets
- Rebuild Overhead – Too many rebuilds can hurt performance.
- Not Suitable for Complex Apps Alone – For large apps, advanced state management (Provider, Riverpod, BLoC) may be required.
- State Tied to Widget Lifecycle – If the widget is destroyed, its state is lost.
Best Practices for Stateful Widgets
- Keep State Local – Only store state inside a Stateful Widget if it belongs to that widget.
- Minimize
setState()Calls – Only wrap the smallest part of UI that needs rebuilding. - Use
constWidgets – For static parts of the UI to avoid unnecessary rebuilds. - Break Down UI – Use smaller reusable widgets inside your Stateful Widget.
- Plan State Management – For large-scale apps, consider advanced solutions like Provider, Riverpod, or BLoC.
Example: Improved CounterApp with Styling
Let’s improve the CounterApp by adding better design:
import 'package:flutter/material.dart';
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Counter App Example")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Count: $count",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text("Increment"),
),
],
),
),
);
}
}
Features of This Version:
- Uses
Scaffoldfor structure. - Adds an
AppBar. - Centers content with
Centerwidget. - Improves UI with
SizedBoxand styling.
Debugging Stateful Widgets
When working with Stateful Widgets, debugging rebuilds is important. You can:
- Use
print()insidebuild()to track rebuilds. - Use Flutter DevTools to monitor widget rebuilds.
- Use
constfor widgets that don’t need to rebuild.
When to Choose Stateful vs Stateless
| Scenario | Widget Type |
|---|---|
| Static text, icons, images | Stateless |
| UI changes on button click | Stateful |
| Fetching and displaying API data | Stateful |
| Navigation structure (like routes) | Stateless |
| User forms and inputs | Stateful |
Leave a Reply