Flutter: State Management with Stateful Widgets

Flutter

In this blog series, we started with an introduction to Dart and Flutter. We discussed the reasons for Flutter’s popularity and why it can be a framework of choice. Along with other things, we also talked about Flutter widgets and how we can compose them to create beautiful user interfaces. In continuation, let’s today talk about the Stateless and Stateful widgets and see how we can use Stateful widgets, in conjunction with setState() method, to manage the state of our application.

Flutter Widgets

Flutter ships with a huge collection of visual, layout, interactive and platform widgets. With reference to managing the state of an application, these pre-built widgets can be broadly categorised into Stateless and Stateful (though InheritedWidget is also there, but we will leave it for now.)

  • A stateless widget never changes e.g. Text, Icon, IconButton, FloatingActionButton, etc. 

For example, when we press an IconButton, it has the ability to handle our interaction with it, but it itself doesn’t change. These widgets inherit from the StatelessWidget class.

  • A stateful widget can change its appearance in response to user interaction or when it receives data e.g. Checkbox, Radio, Slider, TextField, etc. We can, for example, move the Slider thumb and change its value. This change in value changes its appearance. These widgets subclass the StatefulWidget class.

Can we Create Custom Stateless and Stateful widgets?  

Flutter has the following Widget class hierarchy. We can extend either the StatelessWidget class  or the StatefulWidget class to define our custom stateless or stateful widgets respectively.

1

Defining a Custom Stateless Widget

  • Let’s take this example that defines a custom widget – MovieName – to display a movie name and count on screen.
  • The widget is configured to have two instance variables, name and count, to store movie name and count respectively. 
  • Its constructor initializes the instance variable name, though count has a constant value.
  • Its build() method creates the child widgets.

2

  • A stateless widget is immutable because its instance variables are always final. In this example, – name and count – are final and have been initialized once. Any attempt to reinitialize them will throw an error. Notice ‘++count’ in the build method. It is an error as this widget is stateless and doesn’t allow to update its properties. 
  • A stateless widget must always override the build method. This method takes BuildContext as a parameter and returns a widget (or a tree of widgets). This returned widget gets added as a child to the parent widget. This example (with no ++count error) will create a stateless widget MovieName with Text widget as its child.
  • The child Text widget accesses and outputs the name and count properties of its parent.

How is a Stateless Widget processed?

‘Widgets are just configurations for pieces of UI’ that we define as developers. Each widget class has a corresponding Element class and a method to create its instance. For example, the StatelessWidget class has a corresponding StatelessElement class and a createElement() method to create its instance.  It is this element that actually represents what is displayed on the device screen.

3

In the above diagram, 

  • When the MovieName instance is created and mounted on the Widget Tree, Flutter asks this widget to give an element that it can display on screen.
  • This stateless widget calls the createElement() method and creates a StatelessElement instance. This instance gets mounted on the Element Tree. It holds a reference to its widget instance, its spot in the UI hierarchy (BuildContext) and manages the  parent-child relationship (lifecycle). It represents the actual piece of UI that is painted on the screen.
  • The MovieName widget has Text as its child. So the element calls its build() method and creates an instance of the Text widget, which gets mounted on the Widget Tree.  
  • The Text widget is also stateless so it calls the createElement() method to create a stateless element. This is created and mounted on the Element Tree. This element holds reference to the Text widget instance and represents what is actually painted on the screen.  
  • If there were more child widgets then the same process would have continued.

Flutter is Declarative

Little off topic, but we must know that Flutter is declarative. It does not follow the procedural approach of calling some update or set methods to modify a UI on state change. Instead, Flutter rebuilds and replaces parts of a UI when state changes.

In the following example, let’s say that the state of ViewA changes and it needs to replace the ‘Save’ and ‘Cancel’ buttons with a ‘Submit’ button. Rather than procedurally calling some update and remove methods on ViewA to modify the UI, Flutter would rather rebuild the ViewA completely.

4

What is State? 

State is the values of all the variables needed to make up the user interface.

Why Stateful Widget?

Stateless Widget:

  • It is immutable. It can’t change its data (state) over time.
  • Since Flutter is declarative, a UI has to rebuild on state change. But StatelessWidget can’t trigger the build method on its own.

Stateful Widget works with two classes:

  • A StatefulWidget subclass that defines the widget. It is immutable.
  • A State subclass to hold state (data) for its widget and a build() method to create children for its widget.

Defining a Custom Stateful Widget

As before, this example defines a custom widget to display a movie name and count on screen but this time it creates a stateful widget. A stateful widget creates two classes – MovieName (widget class) and _MovieNameState (state class). 

The widget class

  • The widget class MovieName configures the widget with one final instance variable name, initialized via its constructor. It is immutable.
  • It also overrides the createState() method that creates a state object of type _MovieNameState.

The state class 

  • _MovieNameState name is optional. Any name can be chosen though such a name shows some relation between the widget and state classes.
  • With _(underscore), this class is private as preferably we would like to associate a state object with its widget only.
  • The state class _MovieNameState is responsible to maintain the state of the widget. It has only one property count
  • It has a build() method to create children for its widget.

5

  • The Text widget has been wrapped with a GestureDetector to make it interactive. It displays widget.name (the widget property is accessed like this in the state class) and count.
  •  When the user taps the text on screen, its handler gets executed, which in turn calls the setState() method. 
  • The setState() method has two responsibilities:
    • To update the state property with a new value
  • Mark its element dirty so that a rebuild can be triggered.
  • The setState() method takes a callback function which increments count and updates the state object.  
  •  It then triggers a rebuild, which creates the child widgets again with an updated state. They are then repainted and we see an incremented value of count on screen.

How is a Stateful Widget Processed?   

  • When the MovieName instance is created and mounted on the Widget Tree, Flutter asks this widget to give an element that it can display on screen.
  • This  Stateful Widget calls the createElement() method but creates a StatefulElement instance this time. This instance gets mounted on the Element Tree, holding a reference to its widget.
  • The Stateful Widget then calls its createState() method to create the State object. This State object holds onto the Stateful Element. 
  • The Stateful Element then calls the State’s build method to create the child widgets. 
  • The child widgets get created and mounted on the Widget Tree. They then create their elements, which get mounted on the Element Tree and so on.

6

How is the UI Updated?

In this example, when the user taps the text on screen,

  • The setState() method is called, which increments count and updates the state object. It also marks the Stateful Element dirty.
    • Because it’s dirty, the Stateful Element calls the State object’s build method and rebuilds the children (for simplicity let’s consider Text widget only) with updated state.
    • The old child widget – Text –  is thrown away and the new one is added to the tree with a new value.
    • As the new child – Text –  is of the same type as the old one, the Stateless Element remains where it is. It only updates its reference to point to this new Text widget.
  • When the Text widget goes for re-rendering, its Element that points to the new Text widget is referred to and we see the new value on screen.

State can be conceptualized as local state and global state

  • Local: State that is needed only in the widget itself and not across the application e.g. the selected tab in a bottom navigation bar or the current page while doing pagination.
  • Global: State that we want to use across different parts of our app e.g. user settings in a Whatsapp application or a list of favorite meals in a meals app.

Local State

Let’s take another example which shows a list of movies (for simplicity, I have just hard coded two list items.) Each movie is a MovieTile with two items –  Text (name) and FavoriteIcon ( IconButton). The user can click on the favorite icon to mark the movie as favorite. 

As the state is needed locally inside the FavoriteIcon (IconButton) widget, this widget is made stateful. The isFavorite boolean variable manages the state of the icon button. It toggles between true and false as the user taps it. This initiates a rebuild and FavoriteIcon updates accordingly. Here’s the full code.

7

Global State 

Lifting State Up: In this example, when the user selects his favorite movie by tapping the icon button, the color of the movie name also gets darker. This means that it’s no longer a local state of the FavoriteIcon, as both the IconButton and the Text widget need it to update themselves. 

To implement this, we’ll have to lift the state up to the parent – MovieTileso that the state can flow down to both the children. It’s illustrated in this diagram.

As MovieTile is now maintaining the state of its children,  it’s changed to a stateful widget. The FavoriteIcon doesn’t need to be stateful any more so it’s changed to a stateless widget. You can find the full code here .

Managing State with setState and StatefulWidget can get Complex

  • Local state can be managed well with setState and a StatefulWidget. But stateful widgets are more complex than stateless widgets and they also need memory to maintain their state.
  • State can be managed globally by Lifting the State Up and Callbacks but, for complex apps, if  callbacks need to traverse the entire tree or pass multiple levels down, then it can get complex very soon.
  • Using setState all over an app can become a maintenance nightmare as your State is scattered all over the place
  • You end up mixing UI and business logic, which is not a good coding principle.

Other State Management Options

  • InheritedWidget & InheritedModel
  • Provider package & Scoped Model
  • Redux
  • BLoC / Rx
  • MobX

What’s Next?

References

Related Posts

Leave a comment

Translate »