Stateful Widgets in Flutter

You want a simple way to manage state in the UI of your flutter app? You can easily do it by Creating your own subclasses of StatefulWidget.

If you wan to manage the state of your flutter application, then today’s post is for you.

So, what is Statefull widgets in Flutter?

A stateful widget in flutter can change its appearance in response to user interaction or when it receives data.

For example, when we move the Slider thumb and change its value. This change in value changes its appearance. These widgets subclass the Stateful Widget class.

Managing State Using Stateful Widgets

StatefulWidget class is the fundamental way in Flutter to manage state. A stateful widget rebuilds itself when its state changes. If the state to manage is simple, using stateful widgets is generally good enough.

Stateful widgets use State objects to store the state. When creating your own subclasses of StatefulWidget, you need to override createState() method to return a State object. For each subclass StatefulWidget, there will be a corresponding subclass of State class to manage the state.

The createState() method returns an object of the corresponding subclass of State. The actual state is usually kept as private variables of the subclass of State.

In the subclass of State, you need to implement build() method to return a Widget object. When the state changes, the build() method will be called to get the new widget to update the UI. To trigger the rebuild of the UI, you need to call setState() method explicitly to notify the framework.

The parameter of setState() method is a VoidCallback function that contains the logic to update the internal state. When rebuilding, the build() method uses the latest state to create widget configurations. Widgets are not updated but replaced when necessary.

SelectColor widget in bellow is a typical example of stateful widget.

class SelectColor extends StatefulWidget {
 @override
 _SelectColorState createState() => _SelectColorState();
}

class _SelectColorState extends State {
 final List _colors = ['Red', 'Green', 'Blue'];
 String _selectedColor;
  
 @override
 Widget build(BuildContext context) {
 return Column(
 children: [
 DropdownButton(
 value: _selectedColor,
 items: _colors.map((String color) {
 return DropdownMenuItem(
 value: color,
 child: Text(color),
 );
 }).toList(),
 onChanged: (value) {
 setState(() {
 _selectedColor = value;
 });
 },
 ),
 Text('Selected: ${_selectedColor ?? "}'),
 ],
 );
 }
}

_SelectColorState class is the State implementation for SelectColor widget. _selectedColor is the internal variable that maintains the current selected color.

The value of _selectedColor is used by the DropdownButton widget to determine the selected option to render and the Text widget to determine the text to display.

In the onChanged handler of DropdownButton, setState() method is called to update the value of _selectedColor variable, which notifies the framework to run  _SelectColorState.build() method again to get the new widget configuration to update the UI.

State objects have their own lifecycle. You can override different lifecycle methods in subclasses of State to perform actions on different stages.

Lifecycle methods of State

Name Description
initState() Called when this object is inserted into the widgets tree. Should be used to perform initialization of state.
didChangeDependencies() Called when a dependency of this object changes
didUpdateWidget Called when the widget of this object changes. Old widget is passed as a parameter
build(BuildContext context) Called when the state changes.
deactivate() Called when this object is removed from the widgets tree
dispose() Called when this object is removed from the widgets tree permanently. This method is called after deactivate().

Of the methods listed in above Table, initState() and dispose() methods are easy to understand. These two methods will only be called once during the lifecycle. However, other methods may be invoked multiple times.

The didChangeDependencies() method is typically used when the state object uses inherited widgets. This method is called when an inherited widget changes.

Most of the time, you don’t need to override this method, because the framework calls build() method automatically after a dependency changes.

Sometimes you may need to perform some expensive tasks after a dependency changes. In this case, you should put the logic into didChangeDependencies() method instead of performing the task in build() method.

The reassemble() method is only used during development, for example, during hot reload. This method is not called in release builds. Most of the time, you don’t need to override this method.

The didUpdateWidget() method is called when the state’s widget changes. You should override this method if you need to perform cleanup tasks on the old widget or reuse some state from the old widget.

For example, _TextFieldState class for TextField widget overrides didUpdateWidget() method to initialize TextEditingController object based on the value of the old widget.

The deactivate() method is called when the state object is removed from the widgets tree. This state object may be inserted back to the widgets tree at a different location. You should override this method if the build logic depends on the widget’s location.

For example, FormFieldState class for FormField widget overrides deactivate() method to unregister the current form field from the enclosing form.

In our first code example, the whole content of the widget is built in the build() method, so you can simply call setState() method in the onPressed callback of DropdownButton. If the widget has a complex structure, you can pass down a function that updates the state to the children widgets.

class Counter extends StatefulWidget {
 @override
 _CounterState createState() => _CounterState();
}

class _CounterState extends State {
 int count = 0;
 @override
 Widget build(BuildContext context) {
 return Column(
 children: [
 CounterButton(() {
 setState(() {
 count++;
 });
 }),
 CounterText(count),
 ],
 );
 }
}

class CounterText extends StatelessWidget {
 CounterText(this.count);
 final int count;
 @override
 Widget build(BuildContext context) {
 return Text('Value: ${count ?? "}');
 }
}
class CounterButton extends StatelessWidget {
 CounterButton(this.onPressed);
 final VoidCallback onPressed;
 
 @override
 Widget build(BuildContext context) {
   return RaisedButton(
   child: Text('+'),
   onPressed: onPressed,
   );
  }
}

In above example, the onPressed callback of RaisedButton is set by the constructor parameter of CounterButton. When the CounterButton is used in Counter widget, the provided handler function uses setState() to update the state.

Further Read:

If you still find it a bit confusing about Flutter StatefulWidget, do reach out to me on Twitter or add your comment in the comment box below.

Happy Fluttering. 😎😎