Working with Stateful Widgets
1. Creating StatefulWidget Class
To create a stateful widget in Flutter, you need to extend the StatefulWidget class and implement a createState method that returns an instance of a State subclass.
| Step | Description | Code Example |
|---|---|---|
| 1. Extend StatefulWidget | Define a new class that extends StatefulWidget. |
|
| 2. Implement createState | Override the createState method to return a new instance of your state class. |
|
Example: Basic StatefulWidget Implementation
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Text('Count: $_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_counter++;
});
},
child: Icon(Icons.add),
),
);
}
}
2. Creating State Class (State<T>)
Example: Basic State Class Implementation
class CounterState extends State<CounterWidget> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Center(
child: Text('$_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: Icon(Icons.add),
),
);
}
}
| Property/Method | Description | Usage |
|---|---|---|
| State<T> | Base class for widget state | Extend this class to manage widget state |
| setState() | Updates widget state and triggers rebuild | Call inside event handlers to update UI |
| build() | Rebuilds widget tree when state changes | Override to define UI based on current state |
State<T> class is tied to a specific widget type T, which is defined in the generic parameter.
3. Understanding State Lifecycle
The state lifecycle in Flutter describes how a widget's state is managed and updated throughout its existence. Understanding this lifecycle is crucial for building responsive and efficient Flutter applications.
| Phase | Description | Key Methods |
|---|---|---|
| Initialization | State is created and initialized. | initState() |
| Active | State is actively used and can be updated. | didChangeDependencies(), build() |
| Update | State is updated in response to changes. | didUpdateWidget() |
| Inactive | State is no longer active but not yet disposed. | deactivate() |
| Disposal | State is disposed and cleaned up. | dispose() |
Example: State Lifecycle Methods
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int counter = 0;
@override
void initState() {
super.initState();
// Initialize state here
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Called when dependencies change
}
@override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// Called when widget is updated
}
@override
void deactivate() {
super.deactivate();
// Called when state becomes inactive
}
@override
void dispose() {
super.dispose();
// Clean up resources here
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('State Lifecycle')),
body: Center(
child: Text('Counter: $counter'),
),
);
}
}
initState() method is called once when the state object is created, and dispose() is called when the state is removed from the widget tree.
Key Points
initState()is called once after the state object is created.didChangeDependencies()is called when the widget's dependencies change.didUpdateWidget()is called when the widget is updated with new properties.deactivate()is called when the state becomes inactive but not yet disposed.dispose()is called when the state is removed from the widget tree and should clean up resources.
4. Using setState() Method
The setState() method is a core part of Flutter's widget lifecycle, used to notify the framework that the internal state of a widget has changed and that it needs to rebuild.
| Method | Description | Usage |
|---|---|---|
| setState() | Triggers a rebuild of the widget's subtree | Call this inside a widget's state class to update UI |
Example: Updating Counter Value
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $counter'),
ElevatedButton(
onPressed: incrementCounter,
child: Text('Increment'),
),
],
);
}
}
setState() method must be called inside the widget's state class, and it should only be used to update the state that affects the UI.
5. Accessing Widget Properties
| Property | Type | Description |
|---|---|---|
| context | BuildContext |
Provides access to the widget's context, allowing for theme data and inherited widgets. |
| widget | Widget |
Represents the widget instance that is currently being built. |
| mounted | bool |
Indicates whether the widget is currently mounted in the widget tree. |
Example: Using context to access theme data
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Text(
'Text color: ${theme.primaryColor}',
style: TextStyle(color: theme.primaryColor),
);
}
}
Example: Using widget to access properties
class MyWidget extends StatefulWidget {
final String title;
MyWidget({required this.title});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return Text(widget.title);
}
}
6. Managing Mutable State Variables
In Flutter, mutable state variables are managed using the StatefulWidget class and its associated _MyWidgetState class. These variables can be modified during the widget's lifecycle, and changes to them trigger a rebuild of the widget.
| Property | Description | Example |
|---|---|---|
| statefulWidget | Represents the stateful widget instance. | MyStatefulWidget |
| state | Represents the current state of the widget. | _MyWidgetState |
| setState | Method to update the state and trigger a rebuild. | setState(() { ... }); |
Example: Updating State Variables
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Text('Count: $_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
setState method must be called to notify the framework that the state has changed, ensuring the UI updates accordingly.
7. Implementing didChangeDependencies() Method
The didChangeDependencies() method is a lifecycle callback in Flutter widgets that is invoked when the dependencies of a widget change. It's particularly useful for widgets that depend on values from BuildContext, such as those using Theme.of(context) or MediaQuery.of(context).
| Method | Description | Usage |
|---|---|---|
| didChangeDependencies() | Called when a dependency of this widget changes. | Override to react to changes in dependencies like theme or media queries. |
Example: Using didChangeDependencies()
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
// React to theme or media query changes
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
}
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).primaryColor,
);
}
}
8. Implementing didUpdateWidget() Method
The didUpdateWidget() method is a lifecycle callback in Flutter widgets that is invoked whenever the widget is rebuilt with new configuration parameters. It's particularly useful for handling updates to widget properties and performing necessary actions when the widget's configuration changes.
| Method | Description | Usage |
|---|---|---|
| didUpdateWidget() | Called when a widget is rebuilt with new properties. | Override to handle property updates and perform actions based on changes in widget configuration. |
Example: Implementing didUpdateWidget()
class MyWidget extends StatefulWidget {
final String title;
const MyWidget({Key? key, required this.title}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late String currentTitle;
@override
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.title != widget.title) {
setState(() {
currentTitle = widget.title;
});
}
}
@override
Widget build(BuildContext context) {
return Text(currentTitle);
}
}
didUpdateWidget() method is called after the widget is rebuilt with new properties. It's important to call super.didUpdateWidget(oldWidget) to ensure proper state management.
9. Cleaning Up Resources
When working with stateful widgets in Flutter, it's important to properly manage resources to avoid memory leaks and ensure efficient app performance. The StatefulWidget lifecycle provides methods to clean up resources when the widget is removed from the tree.
| Method | Description | Usage |
|---|---|---|
| dispose() | Called when the widget is removed from the tree and is about to be discarded. | Override this method to clean up resources such as timers, streams, or subscriptions. |
Example: Cleaning Up a Timer
class TimerWidget extends StatefulWidget {
@override
_TimerWidgetState createState() => _TimerWidgetState();
}
class _TimerWidgetState extends State<TimerWidget> {
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
print('Timer tick');
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
child: Text('Timer running...'),
);
}
}
dispose method to prevent memory leaks.
10. Using mounted Property
The mounted property is a boolean flag available in Flutter widgets that indicates whether the widget is currently part of the widget tree. It's particularly useful for checking if a widget is still active before performing operations that might cause errors if the widget has been disposed.
| Property | Description | Usage |
|---|---|---|
| mounted | A boolean indicating if the widget is still in the widget tree. | Used to prevent operations on disposed widgets. |
Example: Checking mounted before updating state
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String data = "Initial";
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(data),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// Simulate an async operation
await Future.delayed(Duration(seconds: 2));
// Check if widget is still mounted before updating state
if (mounted) {
setState(() {
data = "Updated";
});
}
},
child: Icon(Icons.refresh),
),
);
}
}
mounted property is particularly important in asynchronous operations to prevent errors when trying to update the state of a widget that has already been removed from the widget tree.
11. Understanding State Object Persistence
In Flutter, state objects are tied to the widget's lifecycle and are managed by the framework. When a widget is rebuilt, its state object persists across rebuilds unless explicitly disposed or replaced.
| Aspect | Description | Notes |
|---|---|---|
| State Object Lifecycle | State objects are created when the widget is first built and destroyed when the widget is removed from the tree. | State objects are tied to a specific instance of a widget and are not recreated during rebuilds. |
| Rebuilds and State | When a widget rebuilds, the same state object is reused. | This ensures that data and UI state are preserved across rebuilds, such as when calling setState(). |
| State Persistence Across Rebuilds | State persists through rebuilds but not across widget instances. | If a widget is removed and re-added to the tree, a new state object is created. |
Example: State Persistence in a Counter Widget
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Text('Count: $count'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
count++;
});
},
child: Icon(Icons.add),
),
);
}
}
_CounterWidgetState object persists across rebuilds, ensuring that the count value remains consistent even if the widget is rebuilt due to a state change.