This tutorial shows you how to use the StatefulBuilder
widget in Flutter.
One thing you can do to improve the performance of a Flutter application is by preventing unnecessary widget builds. That can have a significant effect especially if you can eliminate unnecessary rebuilding of expensive widgets. In this tutorial, I am going to explain how to remove unnecessary builds by using StatefulBuilder
.
Using StatefulBuilder
For example, we have a widget whose children are a Switch
widget and another widget. The Switch
widget requires an argument named value
which determines whether it's on or off. Usually, the value passed to the argument is stored in a state variable. When the switch is pressed, the state variable is updated and the build
method of the State
class is invoked. That means the widgets returned by the build
method may be rebuilt.
To make it easy to notice that a widget is rebuilt, I added the print
statements in the build
methods.
class _MyWidgetState extends State<MyWidget> {
bool _isOn = false;
@override
Widget build(BuildContext context) {
print('MyWidgetState - build');
return Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Switch(
value: _isOn,
onChanged: (newValue) {
setState(() {
_isOn = newValue;
});
},
),
MyAnotherWidget(),
],
),
)
],
);
}
}
class MyAnotherWidget extends StatelessWidget {
MyAnotherWidget({super.key});
@override
Widget build(BuildContext context) {
print('MyAnotherWidget - build');
return const Text('Another widget');
}
}
Below is the output every time you toggle the switch.
MyWidgetState - build
MyAnotherWidget - build
If you run the code, every time the Switch
is toggled, the build
method of _MyWidgetState
is re-invoked. As a result, it will also invoke the build
method of MyAnotherWidget
.
In some cases, certain widgets do not need to be rebuilt. For example, in the code above, MyAnotherWidget
doesn't use the _isOn
state variable. As a result, it doesn't need to be rebuilt every time the user toggles the Switch
. You can prevent rebuilds by using a const
constructor if possible. If that's not possible, one of the alternatives is using StatefulBuilder
.
const StatefulBuilder({
Key? key,
required StatefulWidgetBuilder builder,
});
To use the StatefulBuilder
widget, you have to pass a StatefulWidgetBuilder
function as the builder
argument. The function is used to build a widget. It has two positional parameters whose types in order are BuildContext
and StateSetter
. The StateSetter
itself is a function that's used to trigger a rebuild. The usage is similar to State.setState
where you need to pass a function for setting the new values for the state variables.
When the StateSetter
function is called, it will invoke the StatefulWidgetBuilder
function. Therefore, only the widgets returned by the builder
will be rebuilt. Since the builder
function is re-invoked every time the StateSetter
is called, the variables used to store the states should be defined outside the builder
function.
@override
Widget build(BuildContext context) {
print('MyWidgetState - build');
return Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
StatefulBuilder(builder: (context, setState) {
print('StatefulWidgetBuilder - build');
return Switch(
value: _isOn,
onChanged: (newValue) {
setState(() {
_isOn = newValue;
});
},
);
}),
MyAnotherWidget(),
],
),
)
],
);
}
If you run the code, you'll notice the difference that it only rebuilds the widget returned by the StatefulWidgetBuilder
when the toggle is pressed.
StatefulWidgetBuilder - build
StatefulBuilder
Parameters
Below is the list of parameters of the StatefulBuilder
constructor.
Key? key
: The widget's key, used to control how a widget is replaced with another.required StatefulWidgetBuilder builder
: The widget's key, used to control how a widget is replaced with another.
Full Code
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Woolha.com Flutter Tutorial',
theme: ThemeData.light().copyWith(
bottomAppBarTheme: const BottomAppBarTheme(
color: Colors.teal,
)
),
home: const MyPage(),
);
}
}
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Woolha.com Flutter Tutorial'),
backgroundColor: Colors.teal,
),
body: const MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool _isOn = false;
@override
Widget build(BuildContext context) {
print('MyWidgetState - build');
return Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Switch(
// value: _isOn,
// onChanged: (newValue) {
// setState(() {
// _isOn = newValue;
// });
// },
// ),
StatefulBuilder(builder: (context, setState) {
print('StatefulWidgetBuilder - build');
return Switch(
value: _isOn,
onChanged: (newValue) {
setState(() {
_isOn = newValue;
});
},
);
}),
MyAnotherWidget(),
],
),
)
],
);
}
}
class MyAnotherWidget extends StatelessWidget {
MyAnotherWidget({super.key});
@override
Widget build(BuildContext context) {
print('MyAnotherWidget - build');
return const Text('Another widget');
}
}
Summary
The StatefulBuilder
widget can be used to eliminate unnecessary rebuilds. The usage is quite simple, you need to pass a StatefulWidgetBuilder
function, which returns a child widget. Inside the function, you can update the states and Flutter will only rebuild the widget returned by the StatefulWidgetBuilder
function.