Flutter setState - The simplest state management in Flutter

Learn the easiest and most fundamental state management tool in Flutter that everyone skips when starting Flutter development.

Flutter setState - The simplest state management in Flutter
Photo by Oleg Laptev on Unsplash

We will discuss setState(); the easiest and simplest state management approach in Flutter. Many great state management libraries are available in Flutter, but it is important to know the most basic approach to update the UI state of your app.

You might be tempted to jump to learning BLoC, Provider, Riverpod, and the thousands of other frameworks available, but learning the fundamentals is more important than complex solutions. Once you understand the basics, you might even decide to make your own state management framework.

What is the setState method?

Stateful widgets have this method called setState() which tells the Flutter framework that this StatefulWidget's state has been updated and needs to be replaced.

setState syntax

When setState() method is called, the State of the StatefulWidget gets replaced with a new one, where the updates inside declared inside the setState() function will be applied.

How do you use setState?

It is good practice to call setState() from one-time events such as button presses or other user-input triggers.

To make setState work as intended, you have to update a state in your app inside the callback argument of this method. For example, if we want to increase the counter when pressing a button and want to display this updated value on the screen, we can write the code as such:

Using setState inside onTap callback

In the sample code, the state that we want to update is the _counter  member variable. Every time we tap the button, the counter is updated by one. Subsequently, the widget is redrawn from the screen and uses this updated value.

The setState() method should not be called in the State's build() function. The reason is that every time you call setState() and there is a new state, the build function will be called recursively.

⚠️
setState() should not be called in the build() function and should only be triggered from user events such as button clicks.

Simple Example - Changing the theme colour

Let's put our setState() knowledge into practice by building a simple desktop app that changes its theme when pressing a button. In this example, we will put the whole app in one file to see how setState() works the best.

SimpleThemeColorApp behaviour when triggering the setState() method

Remember that setState() only works on StatefulWidget so for this simple example, we will build our app as a StatefulWidget.

simple_theme_color_app.dart

We built the SimpleThemeColorApp widget which extends a StatefulWidget to add an option to save state. The state of this app is the _materialColor which defaults to blue.

Demo app using setState to update the theme

In order to update the theme, we need to add an onTap listener in the buttons that call the setState() method.  Do not forget to add the callback that sets the new value of the _materialColor.

Updating the state without setState

You might think that you can just set the _materialColor property without setState(). If you do this, the _materialColor property will be updated; the screen however will not.

The reason behind this behaviour comes from the idea that widgets are immutable. Widgets cannot change once rendered BUT they can be replaced.

Changing the StatefulWidget's state through setState() triggers the rebuilding of this widget. Once this widget has been rebuilt, you can see the updated display.

Complex example - Changing the theme colour outside the StatefulWidget

It gets a bit trickier when you want to update the state of a widget from another widget. Let's look at an example where the Theme of the app is declared at the topmost parent widget of the tree.

If we have independent and separate widgets for different screens in the app, they would not have access to the state of the topmost parent widget. So how do we update the state from these widgets?

We can pass callbacks as part of the widget's constructor parameters. These callbacks can be defined when the parent widget creates these child widgets and pass the setState() function altogether.

complex_theme_color_app.dart

In the code example, our topmost parent widget is the ComplexThemeColorApp. The state member of this widget (_ComplexThemeColorAppState) keeps the theme colour variable _materialColor which is used to set the theme colour of the app.

The child widget PageWidget will be the screen that contains the buttons to change the theme. Note that when constructing this widget, it requires a function parameter called updateThemeCallback to update the state. This function accepts a MaterialColor as a parameter.

Let's define the PageWidget as a row containing two buttons; one for each colour. Since this widget has the callback, we can set this as the function to be called when we tap each of the buttons, each with its corresponding colour.

page_widget.dart

The resulting app will be the same as the one shown in the basic example and only the code implementation is different.

Limitations

You will notice that this approach is not scalable. It works on widget trees that are one, or even two levels deep, but not more than that. Adding callbacks can be a nightmare especially when you start building complex widget trees.

The ideal approach to solve this problem is for a widget to be able to access a state within its scope, update it, and the widgets listening to the state will be reconstructed and updated.

Enter the state management frameworks

The setState() method is best used for updating the state of a single StatefulWidget. It's time to use an appropriate state management framework when your app starts to become more complex.

The Flutter development team has suggested a number of frameworks to use to handle states. I have mentioned these libraries in the introduction (BLoC, Provider, Riverpod, etc) and are all well-suited for handling states in Flutter.

We will discuss these better state management frameworks in another article.


Summary

Widgets are immutable by nature therefore we need to rebuild the widgets if we want to display a new state. The simplest way to update the state of a StatefulWidget is by using setState(). To use setState(), update the member variable of your widget in the callback function of setState(). This will rebuild the widget with the new value which then updates the display of your app.

References