State Management In Flutter
Published: 2020-05-02 00:39:09


Prerequisites

Tools required to follow the article.

- Flutter SDK.
- Android Studio or VS code.
- Emulator or an Android device


Introduction

According to Wikipedia, Flutter is an open-source UI software development kit created by Google. It is used to develop applications for Android, iOS, Windows, Mac, Linux, Google Fuchsia and the web  using Dart language .  

In Flutter, everything is a widgets which have states, otherwise known as information rendered by the widgets. The state of your application is therefore important to consider, how widgets, content change base on actions and how data is passed on from one widget to another widget in the tree is very essential for the run-time performance of your application. 

> State is information that can be read synchronously when the widget is built and might change during the lifetime of the widget.



What We are Building

At the end of this article, we will show how set state and how to pass data from parent widget to the child widget.

Widget tree is a structure that represents how our widgets are organized.
Below is a widget tree that describes how the application we will be building.

    
![figure 1 : Widget Tree of The Application](https://paper-attachments.dropbox.com/s_5FFA95AC7D57CA650C30D0E12AE5C15226B21984D3FE4E72A7009966A6A5D7A8_1577544827288_wiget_tree.jpg)

Setting up Project Tools

Create a new app with the `flutter create app_name`command and open the project on your preferred IDE.


From our `figure 1` above we are going to create three stateless classes each representing our different levels in the tree.
Replace your `main.dart` with the following code:


    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Level1(),
        );
      }
    }
    
    class Level1 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
          centerTitle: true,
            title: Text('Cy is 10'),
          ),
          body: Level2(),
        );
      }
    }
    
    class Level2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Level3(),
        );
      }
    }
    
    class Level3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text('My Real Age is 10');
      }
    }
    


The next line of code we see is

    void main() => runApp(MyApp());

Dart programs has an entry point called `main`. When we run Flutter or dart file it first runs main function. In this case the main function is calling flutter specific function called `runApp` which takes any widget as an argument and created a layout which fills the screen in our case it takes `MyApp` as it argument.
`MyApp` is a stateless widget which returns `MaterialApp` widgetwhich contains three arguments title, theme and home.  Home in our takes   `Level1`  as it argument. `Level1` returns a Scaffold widget which has an `AppBar` widget  that contains a `Text` widget as its title. Also, the Scaffold widget has `Level2` as its body. Level2` returns a `Container` widget which has `Level3` as it child argument, finally `Level3`returns a `Text` widget which prints out some strings to the screen. 
If you noticed there is a widget that is missing from our code that is the button that will increment my age any time it's pressed. So you can guess which level we will be implementing that. Hope you guessed right, our button will be in our `Level2` widget but it will be a `Column` so it contains both `Level3` And `Button`.

So let’s update our `Level2 class` with the following code:


    class Level2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(),
                FlatButton(
                  color: Colors.blue,
                  onPressed: () {},
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }


State Management Techniques

There are several ways to manage the state of an application, especially in Flutter. None of these ways is termed “best”. For me, I would say choose a technique according to the complexity of your application. 
These are different ways to manage state in a Flutter application which includes
SetState, Lifting State Up, Provider, Inherited Widget, Bloc, MVC, Scoped Model, MobX, Redux

For the lesson, we are going to look into the first three which is SetState, Lifting State Up and Provider.

SetState

Our widgets are either Stateless or Stateful widget.
Stateless Widget: As the name implies, it a widget which cannot change its properties throughout the run-time of the application. You can as well call a static widget. For Properties to Change It has to be rebuilt for changes to reflect on the widget. As you can see from our code all our widgets from `MyApp` to `level3` are stateless widgets.
Stateful Widget: This can be said to be the opposite of a Stateless Widget. This allows you to change the state of widget even during the run-time of the application. 
So for us to be able to increment our Age in our application when the button is clicked, one of our widgets will have to change to Stateful Widget and if you are quick enough you will notice it our `Level2` 
But first, we need to make the age number in `level3` dynamic. so let us update our `Level3` first just above the `@override` add the following lines code:

    
    final int age;
    Level3(this.age);

And replace the `Text` widget with the code below:


    Text(
      'My Real Age is $age',
      style: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.bold,
      ),
    );

Our `Level3`  now looks like this:

    class Level3 extends StatelessWidget {
      final int age;
      Level3(this.age);
      @override
      Widget build(BuildContext context) {
        return Text(
          'My Real Age is $age',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        );
      }
    }

Let’s move over to `Level2` and make first make it a Stateful widget.
Replace the `Level2` with the following code:


    class _Level2State extends State<Level2> {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(age),
                FlatButton(
                  color: Colors.blue,
                  onPressed: () {},
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }

We need to create an integer variable called `age` to hold an initial age value of 10, and we do that in the private class of the widget, the `_Level2State` 
add the following code after the opening brace:


    int age = 10;

You have to pass the variable `age` into the `Level3` widget, update the how it’s been called:


    Level3(age),

Now Let’s use `setState` to add to our age. If you noticed the flat button has an `onPressed` which requires a callback function that is triggered only when the button is pressed. So let's add our function to update the age.
update the `onPressed` function with the code below:


    onPressed: () {
      setState(() {
        age++; 
      });
    },

Your `Level2` should look like this:


    class _Level2State extends State<Level2> {
      int age = 10;
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(age),
                FlatButton(
                  color: Colors.blue,
                  onPressed: () {
                    setState(() {
                      age++;
                    });
                  },
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }
    

After this, if we run the application, when you press the button you notice the age increases each time the button is pressed. But we still have a problem and that is we are not able to update the age value on our `AppBar` widget. So to this, we have to lift the states.

Lifting State Up

This is a process of defining and setting all states at a high level in the tree so that all the children of the widget will be able to access. 
Since our `AppBar` needs to access the age variable and change when it is pressed, we have to lift the state to the parent widget in our case is our `Level1` widget.
So first we need to do the same as we did to our `Level2` widget, change it stateful widget and declare our variable age. 
Now we can add make the access the age in the `AppBar`, update the `AppBar` to:


    appBar: AppBar(
      centerTitle: true,
      title: Text('Cy is $age'),
    ),

`Level1` should look like:


    class Level1 extends StatefulWidget {
      @override
      _Level1State createState() => _Level1State();
    }
    
    class _Level1State extends State<Level1> {
      int age = 10;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: Text('Cy is $age'),
          ),
          body: Level2(),
        );
      }
    }

We also Have to anticipate the age variable in our `Level2` and change it back to stateless widget since we will be handling the state in the parent widget
 Replace the `Level2` with the code below:
  

     class Level2 extends StatelessWidget {
      final int age;
      Level2({this.age});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(age),
                FlatButton(
                  color: Colors.blue,
                  onPressed: () {
                    setState(() {
                      age++;
                    });
                  },
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }

also, pass `age` into the `Level2` from `Level1`, update the body with:


    body: Level2(age: age,),

Now we observe that an error is indicated in the line that has the `setState` in the `Level2` widget, that is telling us that we can’t set states in a stateless widget. So we go ahead and empty that function.
Now that both the `AppBar` and other widget bellow the tree can access the `age` variable let’s create a function that will update when the button is pressed. 
Just after where you declared the variable `age` in `Level1` let’s create the function:


    void addAge() {
      setState(() {
        age++;
      });
    }

Since our button is in `Level2` we have to pass it down as props, so we need to anticipate this function in `Level2` so update your `Level2` constructor with the following:


    final int age;
    final Function onPressedFunction;
    Level2({this.age, this.onPressedFunction});

now we can replace our `onPressed` in `Level2` with the code bellow:


    onPressed: onPressedFunction,

so let’s pass the function down to `Level2` in `Level1` :


    body: Level2(
      age: age,
      onPressedFunction: addAge,
    ),

Our `Level1` widget looks like :


    class Level1 extends StatefulWidget {
      @override
      _Level1State createState() => _Level1State();
    }
    
    class _Level1State extends State<Level1> {
      int age = 10;
    
      void addAge() {
        setState(() {
          age++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: Text('Cy is $age'),
          ),
          body: Level2(
            age: age,
            onPressedFunction: addAge,
          ),
        );
      }
    }

And `Level2` looks like :

    class Level2 extends StatelessWidget {
      final int age;
      final Function onPressedFunction;
      Level2({this.age, this.onPressedFunction});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(age),
                FlatButton(
                  color: Colors.blue,
                  onPressed: onPressedFunction,
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }

And `Level3` 

    class Level3 extends StatelessWidget {
      final int age;
      Level3(this.age);
    
      @override
      Widget build(BuildContext context) {
        return Text(
          'My Real Age is $age',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        );
      }
    }

Restarting the application and pressing  the button you can notice that both the text in the `AppBar` and the `Text` in `Level3` changes simultaneously.
So basically we have been able to pass data from the top widget in the tree down to the last child in the tree and also update it with an action button which is in the middle level of the tree. You can as well take a bow cause our aim has been achieved right but I will introduce you to a better way of handling state with a package made available and recommended to us by the flutter team at Google


Provider

 Provider is packaged is maintained by the flutter team and it is the recommended way of managing the state of an application. So let’s see how to use it in our application. 
 

> So if you are wondering why we are introducing Provider to our app, here is a simple reason, we want to access data anywhere in our widget tree without having to pass them from one widget to another.

 
 First of all, we need to install the package to our application, click here to see the full documentation of the package. Let’s add it to our `pubspec.yaml`, after `cupertino_icons` lets, add the following code:
 

     provider: ^4.0.0 //4.0.0 is the current version as at when this article was written */

click on `package get` to download the packages.
Going back to our `main.dart` import provider so we can have access to the classes we will be needing, at the beginning of our file add this


    import 'package:provider/provider.dart';

 
For us to spread our data and modify them we will need a class that extends a flutter class called `ChangeNotifier`, and defined our age and function to increment our age
 At the bottom of our `main.dart` add the following code:
 

    class AgeClass extends ChangeNotifier {
      int age = 10;
    }

Let’s start using our provider package in our application. the package is always defined at the top of the widget tree. So let's go over to our `MyApp` and update what it is returning with the following: 


    return ChangeNotifierProvider<AgeClass>(
      create: (context) => AgeClass(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Level1(),
      ),
    );

From the code we just added, we have a new widget from the provider package called `ChangeNotifierProvider` and the type of data we are expecting in return, in our case we are expecting the `AgeClass` which we add defined. Also it `ChangeNotifierProvider` requires a `create`  function that takes the current context as an argument and returns the actual data, in our case it returning the `AgeClass` because that is where our data is stored. 
Now our data is can be accessed with a `Provider.``of``<*dataType expected*>(context)`
so let’s go ahead and all our data where we need it, first the `AppBar` let’s update the `title` widget in `Level1` with :


    title: Text('Cy is ${Provider.of<AgeClass>(context).age}'),

 
 then `Text` in `Level3` with:
 

     Text(
      'My Real Age is ${Provider.of<AgeClass>(context).age}',
      style: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.bold,
      ),
    );

Now if you run the app you will realize the function in the add button is not function let’s create that immediately. In the `AgeClass`, we will create a function to do this and notify every widget that is listening to data that is been modified and trigger a rebuild of only the widget with the `notifyListeners()` method. 
so let’s add the following code to our `AgeClass` :


    void addAge() {
      age++;
      notifyListeners();
    }

 we have created a function to increment our age let’s go ahead and append it to our `FlatButton`'s `onPress`. Update the `onPressed` with the code below:

    onPressed: Provider.of<AgeClass>(context).addAge,

so you can go ahead and change your `Level1` back to a `stateless` widget
and also delete the variable `age` and the function `addAge` inside of it. Your `Level1` should look like:


    class Level1 extends StatelessWidget {
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: Text('Cy is ${Provider.of<AgeClass>(context).age}'),
          ),
          body: Level2(),
        );
      }
    }

In `Level2`, erase the Function `onPressedFunction` and the constructor for it to look as the code bellow:


    class Level2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(),
                FlatButton(
                  color: Colors.blue,
                  onPressed: Provider.of<AgeClass>(context).addAge,
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }

Also, erase the unused variable `age` in level three and its constructor. it should look like this:


    class Level3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text(
          'My Real Age is ${Provider.of<AgeClass>(context).age}',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        );
      }
    }

And `AgeClass` 


    class AgeClass extends ChangeNotifier {
      int age = 10;
    
      void addAge() {
        age++;
        notifyListeners();
      }
    }

so after following this tutorials your `main.dart` file should look like what I have bellow:


    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<AgeClass>(
          create: (context) => AgeClass(),
          child: MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: Level1(),
          ),
        );
      }
    }
    
    class Level1 extends StatelessWidget {
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: Text('Cy is ${Provider.of<AgeClass>(context).age}'),
          ),
          body: Level2(),
        );
      }
    }
    
    class Level2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Level3(),
                FlatButton(
                  color: Colors.blue,
                  onPressed: Provider.of<AgeClass>(context).addAge,
                  child: Text('Add'),
                )
              ],
            ),
          ),
        );
      }
    }
    
    class Level3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text(
          'My Real Age is ${Provider.of<AgeClass>(context).age}',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        );
      }
    }
    
    class AgeClass extends ChangeNotifier {
      int age = 10;
    
      void addAge() {
        age++;
        notifyListeners();
      }
    }


Conclusion 

During this article, we’ve learned about the different ways of state management, from using SetState and passing it down the three to increase the scope of the state by Lifting States up to a higher widget and using the provider package to handle states properly.

Happy Fluttering