Flutter State Management

In this section, we are going to discuss flutter state management and how we can use it. We know that in Flutter, everything is a widget. Widgets are classified in two categories generally, one is a Stateless widget, and another is a Stateful widget. The Stateless widget does not hold any internal state so we can’t change there data. It means once they are built, we cannot change or modify them until we rebuild them from outside. On the other hand, a Stateful widgets are dynamic in nature because they have state which means we can rebuild the app by changing the state from inside.

What is State?

A state is data which can be read when the widget is built and which might get changed or modified over a lifespan of the app. We can change the widget by updating the state object using the setState() function available for Stateful widgets. The setState() function allows us to change the properties of the state object which triggers the rebuild of the UI.

State management is one of the most important processes in the lifecycle of an application. According to official documentation, Flutter is declarative. It means Flutter builds its UI according to the current state of the app. Figure below explains it in more clarity where UI is built using the application state.

flutter-state

Let us take a simple example to understand the concept of state management. Suppose you have a calculator app. Now, We have to get two numbers and show the result dynamically on the screen. Then,you need to refresh the output according to the input. In this situation we need a state to get the input and show the outputs because every time you make a change or update the state, the UI gets refreshed.

n Flutter, State management is categorized into two conceptual types, which are given below:
  1. Ephemeral State
  2. App State

Ephemeral State

This is known as UI State or local state. This state is related to the specific widget, or it is the state that containstraints to a single widget. In this kind of state, you do not need to use state management techniques. The common example of this state is Text Field.

Example
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  int _i = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(this.widget.title),

      ),ASWEGN 5652	
      body: Center(
        child: GestureDetector(
          onTap: () => setState(() => i++),
          child: Container(
            padding: EdgeInsets.all(10.0),
            decoration: BoxDecoration(
              color: Colors.lightGreen,
              borderRadius: BorderRadius.circular(15.0),
            ),
            child: Text('Clicked $_i Times'),
          ),
        ),
      ),
    );
  }
}

In the example, the _i is a local state. Here, only the setState() function inside the StatefulWidget’s class can access the _i variable. The build method calls a setState() function, on modification in the state variables. Which means when this method gets executed, the widget object is replaced with the modified widget with the new value.

App State

This is different from the ephemeral state. This is the type of state that we want to use across various parts of our app and we want to keep between different user sessions. Thus, this state can be used globally. It is also known as application state or shared state. Some examples of this state are User preferences, Login info, notifications in a social networking app read/unread state of articles in a news app, etc.

The diagram below explains the difference between the ephemeral and the app state more appropriately.

flutter-app-state

We can learn the app state management using the provider package. The state management using the provider package is easy to understand and requires less coding. Provider is a third-party library you can get from https://pub.dev/. Here, we we will work with three main concepts of this library.

  1. ChangeNotifier
  2. ChangeNotifierProvider
  3. Consumer

ChangeNotifier

ChangeNotifier is a simple class in the provider package, it provides change notification to the listeners of this class. It is simple to understand, implement, and optimized for a small number of listeners. It is used by the listener to observe the changes in data. We only used the notifyListener() method for informing the listeners of any change.

For example, let us create a ChangeNotifier class. In this class, the Counter is extended with ChangeNotifier, which notifies its listeners when we call notifyListeners(). It is the only method that needs to be used in the ChangeNotifier extended class. In the example below, we declared two functions the increment and decrement, which are used to increase and decrease the counter. We call notifyListeners() method every time the value changes to update the app’s UI.

import 'package:flutter/material.dart';  
  
class Counter with ChangeNotifier {  
  int _counter; 
  Counter(this._counter);  
  getCounter() => _counter;  
  setCounter(int counter) => _counter = counter;  
  void increment() {  
    _counter++;  
    notifyListeners();  
  }  
  
  void decrement() {  
    _counter--;  
    notifyListeners();  
  }  
}  

ChangeNotifierProvider

ChangeNotifierProvider widget provides an instance of a ChangeNotifier to its descendants. It comes in the provider package. The following code snippets will help us to understand the ChangeNotifierProvider widget.

Here, we have defined a builder which will use an instance of the Counter model. ChangeNotifierProvider does not rebuild CounterModel unless there is a need for updating the widget. It will also automatically call the dispose() method on the Counter model when the instance is not needed any more.

class MyApp extends StatelessWidget {  
  @override  
  Widget build(BuildContext context) {  
    return MaterialApp(  
      theme: ThemeData(  
        primarySwatch: Colors.indigo,  
      ),  
      home: ChangeNotifierProvider(  
        builder: (_) => CounterModel(),  
        child: CounterView(),  
      ),  
    );  
  }  
}  

If we need to provide more than one provider, we can use MultiProvider. The MultiProvider is the list of the different Providers used within the scope. Without using this, we would have to nest our Providers with one being the child of another widget . We can understand this by the below code.

void main() {  
  runApp(  
    MultiProvider(  
      providers: [  
        ChangeNotifierProvider(builder: (context) => Counter()),  
        Provider(builder: (context) => SomeOtherClass()),  
      ],  
      child: MyApp(),  
    ),  
  );  
}  

Consumer

This is a type of provider that doesn’t do anything special. It is used to call the provider in a new widget and delegates it to the builder. The following code explains this feature clearly.

return Consumer(  
  builder: (context, count, child) {  
	return Text("Total price: ${count.total}");  
  },  
);  

In the above example, the consumer widget only uses a builder function, which gets called whenever the ChangeNotifier value changes. The builder function uses three arguments, context, count, and child. Context is contained in every build() method. count is the instance of the ChangeNotifier, and the third argument is the child which is used for optimization. It is the good practice to put the consumer widget as low in the tree as possible.

Subscribe Now