Flutter and Dart Basics

Stateful Widgets


Learning Objectives

  • You know of the concept of state in Flutter.
  • You know how to create stateful widgets.
  • You know how to use the GetX library for managing state.

Application state

Application state refers to the variables and their values in an application. Depending on implementation, application state can influence the behavior of an application, such as changing what is shown to the user. Depending on the used framework, some of the variables can be hidden from the user and the programmer, some can be hidden from the user but not from the programmer, and some of them can also be adjusted by the user through e.g. interaction with the application.

As an example of hidden variables, Flutter has buttons that react to the button press by showing an animation, where the animation is controlled by application state. The example below demonstrates this using the ElevatedButton widget. When you run the program, you see a button with the text “Current count is: 0”. Clicking the button shows a brief visual cue that indicates that the button has been pressed.

Run the program to see the output

Buttons have also functionality that is invoked when pressed. As an example, above, pressing the button calls the function increment that increases the value of the variable count by one. This change is not reflected in the text shown to the user, as the widget is stateless. In other words, even though the value of count changes, the Text widget is not recreated or updated to reflect the change.

Flutter has both stateless and stateful widgets. The primary purpose of stateless widgets is to create widgets that do not change over time. In contrast, stateful widgets are used to create widgets that can change over time. The state of a stateful widget is stored in a separate object that is created when the widget is created. The state object is mutable, meaning that the values of the variables in the object can be changed. Given explicit instructions, when the state object changes, the widget is rebuilt, and the changes are reflected in the user interface.

Loading Exercise...

Here, we briefly look into Flutter’s “vanilla” functionality for building widgets with state. Then, we look into using GetX for state management.

Stateful widget

Stateful widgets are created using StatefulWidget and State. They work in tandem — a class that extends State outlines the logic of the widget and defines how it is built, while a class that extends StatefulWidget defines non-changing variables of the widget (if any) and provides a createState method used to create the widget out of the class extending State.

The following example outlines the interplay of these two classes. We define a class called CountWidget that extends StatefulWidget. The class has a method State<CountWidget> createState() that is used to create an instance of the class _CountState, which in turn extends State. We use type parameters in the class definition for _CountState to explicitly outline that the state is related to the CounterWidget.

Run the program to see the output

When trying out the example, you’ll notice that the text still does not change when the button is pressed. This is because the widget is not aware of the change in the state.

Notifying Flutter about state changes is done using a method setState that is inherited from the class State. The method setState takes a function as a parameter — typically, the changes to the state are defined within the function.

Loading Exercise...

In the following, we have changed the method _increment to use the setState to notify about the change in the value of _count. Now, when the button is pressed, the value of _count is increased by one, and the change is shown to the user.

Run the program to see the output

In practice, calling the setState method leads to a notification of a change in state, which in turn leads to calling the method build. The newly created widget that is returned from the method build replaces the old widget. If the widget includes other widgets, they are also recreated. This is why the text changes when the button is pressed.

Loading Exercise...

The following example outlines a similar application, but now the text is centered on the screen and the button is replaced with an icon that is placed to the lower right corner of the application using Scaffold’s floatingActionButton property. The icon is a heart, and the text shows the number of likes. When the heart is pressed, the number of likes is increased by one.

Run the program to see the output

Loading Exercise...

When applications grow in size, maintaining the state with stateful widgets can become cumbersome, as the state is spread across multiple widgets. For this, Flutter has a range of state management libraries that help in managing the state in a more structured way. Next, we’ll briefly look into the basics of handling state with GetX, which we also looked into in the part on navigation.

State and GetX

GetX provides reactive variables, which are variables that can notify about changes in their values. When using GetX, reactive variables are declared by adding .obs to the variable declaration.

Observing changes in state is done using the Obx widget. The Obx widget takes a function that returns a widget as an argument. When using the Obx widget, GetX calls the function whenever the value of a reactive variable changes, which leads to the recreation of the widget returned by the function, and updating the shown widget in the user interface.

Contrary to “vanilla” Flutter, where stateful widgets require creating a class extending StatefulWidget and a class extending State, GetX handles the state changes internally and can be used with classes that extend the StatelessWidget class.

Loading Exercise...

The following outlines a rewrite of the above application, where the state is managed with GetX. The variable _count is declared as a reactive variable by adding .obs to the variable declaration. The Obx widget is used to observe the variable; whenever the value of _count changes, the text widget is recreated.

Run the program to see the output

Note that, above, the variable _count is not declared as an integer but as var. After declaring an integer variable using .obs, a new variable is created — in this case, the type of the variable is RxInt, which is a part of GetX. The var is used as Flutter can infer the type of the variable from the context, but RxInt would also work.

In other words, the variable _count is not an integer but an object that has the properties of an integer. The object has a method ++ that increases the value of the object by one, and a method -- that decreases the value of the object by one. However, if the value needs to be set, it is done by assigning a new value to the object’s property value, e.g. _count.value = 42.

Loading Exercise...