Explore practical examples of using `setState` in Flutter to manage app state effectively, including a counter app, form input management, visibility toggling, and a mini to-do list project.
setState
State management is a crucial aspect of Flutter development, allowing you to create dynamic, responsive applications. In this section, we’ll explore practical examples of using setState
to manage state in Flutter applications. We’ll cover a simple counter app, managing form inputs, toggling widget visibility, and a mini-project to build a to-do list app. Along the way, we’ll emphasize best practices to ensure efficient and maintainable code.
setState
Before diving into examples, it’s important to understand what setState
does. In Flutter, setState
is a method used within stateful widgets to update the UI in response to state changes. When you call setState
, Flutter knows to redraw the widget tree, reflecting the changes in the UI.
Let’s start with a classic example: a counter app. This simple app will demonstrate how to use setState
to update a counter value displayed on the screen.
import 'package:flutter/material.dart';
void main() => runApp(CounterApp());
class CounterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
CounterScreen
is a stateful widget, meaning it can hold state that changes over time._counter
variable holds the state. The _incrementCounter
method updates this state using setState
._incrementCounter
is called, setState
triggers a rebuild of the widget tree, updating the displayed counter value.Managing user input is a common requirement in apps. We’ll use TextEditingController
and setState
to manage the state of a text field.
import 'package:flutter/material.dart';
void main() => runApp(FormApp());
class FormApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FormScreen(),
);
}
}
class FormScreen extends StatefulWidget {
@override
_FormScreenState createState() => _FormScreenState();
}
class _FormScreenState extends State<FormScreen> {
final TextEditingController _controller = TextEditingController();
String _displayText = '';
void _updateText() {
setState(() {
_displayText = _controller.text;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form Input Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Enter text'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _updateText,
child: Text('Submit'),
),
SizedBox(height: 20),
Text(
'You entered: $_displayText',
style: TextStyle(fontSize: 18),
),
],
),
),
);
}
}
_updateText
method uses setState
to update _displayText
, which is then displayed on the screen.Toggling the visibility of widgets is another common use case for setState
. Let’s create a widget that shows or hides content based on a boolean state variable.
import 'package:flutter/material.dart';
void main() => runApp(ToggleApp());
class ToggleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ToggleScreen(),
);
}
}
class ToggleScreen extends StatefulWidget {
@override
_ToggleScreenState createState() => _ToggleScreenState();
}
class _ToggleScreenState extends State<ToggleScreen> {
bool _isVisible = true;
void _toggleVisibility() {
setState(() {
_isVisible = !_isVisible;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Toggle Visibility Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Visibility(
visible: _isVisible,
child: Text(
'This text is visible',
style: TextStyle(fontSize: 24),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleVisibility,
child: Text(_isVisible ? 'Hide' : 'Show'),
),
],
),
),
);
}
}
Visibility
widget controls the visibility of its child based on the _isVisible
state._toggleVisibility
method uses setState
to toggle the _isVisible
boolean, updating the UI accordingly.Now, let’s build a mini to-do list app. This project will reinforce your understanding of setState
by managing a list of tasks that can be added and marked as complete.
import 'package:flutter/material.dart';
void main() => runApp(TodoApp());
class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TodoScreen(),
);
}
}
class TodoScreen extends StatefulWidget {
@override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final List<String> _tasks = [];
final TextEditingController _taskController = TextEditingController();
void _addTask() {
if (_taskController.text.isNotEmpty) {
setState(() {
_tasks.add(_taskController.text);
_taskController.clear();
});
}
}
void _removeTask(int index) {
setState(() {
_tasks.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('To-Do List'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: _taskController,
decoration: InputDecoration(labelText: 'Add a task'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _addTask,
child: Text('Add Task'),
),
Expanded(
child: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_tasks[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _removeTask(index),
),
);
},
),
),
],
),
),
);
}
}
_tasks
list holds the state of the tasks. Tasks are added and removed using setState
.ListView.builder
dynamically builds the list of tasks, updating the UI whenever the state changes.setState
While setState
is powerful, it’s important to use it wisely:
setState
Only When Necessary: Unnecessary calls to setState
can lead to performance issues. Only call it when the state actually changes.setState
is called within the widget’s state class and that the state is actually changing.setState
is called. Optimize your widget tree to reduce unnecessary rebuilds.Mastering setState
is a fundamental skill in Flutter development. By understanding how to manage state effectively, you can create dynamic, responsive applications. Practice these examples, build your own projects, and explore more complex state management solutions as you advance in your Flutter journey.