State Management Using Provider In Flutter

In this article, we will learn about State Management in Flutter using provider.

Introduction

 
This article explains how the state is managed in Flutter. There is a method you already know about, that is, scoped models. Provider is also a state management technique that is developed by the community, not by Google; however, Google highly encourages it. Now, let’s see Provider in detail. 
 

A brief explanation of provider with real-time example

 
Scenario 1
 
Everyone of you have created a list of customers or products in some programming language. Now, assume you add a new customer or product dynamically in that list. What you need is to refresh the list to view the newly added item into the list. Every time you add a new item, you need to refresh the list. Doing the same many times reduces the performance of the app. State Management is a concept to handle such a situation to improve the performance.
 
Scenario 2
 
IMagine that you have a list on page 1 and you will add a new item on page 2. So, the newly added items will not be reflected on page 1 until you fetch the list again. Here, Provider will help you to manage such situation where any update in the list will be notified everywhere it will be used and automatically update with changes as and when needed.
 
You will find terms like ChangeNotifier, notifyListeners, ChangeNotifierProvider, Consumer, etc. These are the parts of Provider to manage the above scenario.
 
So, let’s understand it by implementing the above example.
 

Steps

 
Step 1
 
The first and most basic step is to create a new application in Flutter. If you are a beginner, you can check my blog Create a first app in Flutter. I have created an app named “flutter_statemanagement_using_provider”.
 
Step 2
 
Add a Dependency in the pubspec.yaml file.
  1. dependencies:  
  2.  flutter:  
  3.    sdk: flutter  
  4.  cupertino_icons: ^0.1.2  
  5.  provider: ^3.0.0+1  
Step 3
 
Now, create one folder under the lib folder and name it as providers. The Providers folder will contain all the data related files. Now, create one file under providers and name it as customers.dart. Below is the code of the customers.dart file. Please read the comments in the code. It will give you a detailed explanation.
  1. // Package for ChangeNotifier class  
  2. import 'package:flutter/foundation.dart';  
  3.   
  4. class CustomerList with ChangeNotifier {  
  5.   // ChangeNotifier : will provide a notifier for any changes in the value to all it's listeners  
  6.   List<Customer> customers = [];  
  7.   CustomerList({this.customers});  
  8.   
  9.   getCustomers() => customers;  
  10.   void addCustomer(Customer customer) {  
  11.     customers.add(customer);  
  12.     notifyListeners(); // Notify all it's listeners about update. If you comment this line then you will see that new added items will not be reflected in the list.  
  13.   }  
  14.   
  15.   void removeCustomer(int index) {  
  16.     customers.removeAt(index);  
  17.     notifyListeners();  
  18.   }  
  19. }  
  20.   
  21. class Customer {  
  22.   // Structure for Customer Data Storage  
  23.   String name;  
  24.   int age;  
  25.   Customer({this.name, this.age});  
  26. }  
Step 4
 
Now, create another folder under lib, named pages. This folder will contain all the pages of the app. Now, create a file under this folder named as new_customer.dart. We are going to create a form to add new customers. Below is the code of the new_customer.dart file. I have created a form to get the customer name and age. Please read the comments in the code; it will give you a detailed explanation.
  1. import 'package:flutter/material.dart';  
  2. import 'package:flutter/services.dart';  
  3. import 'package:flutter_statemanagement_using_provider/providers/customers.dart';  
  4.   
  5. class NewCustomer extends StatefulWidget {  
  6.   final customerList; // stores object of listener passed from calling class  
  7.   NewCustomer({Key key, this.customerList}) : super(key: key);  
  8.   
  9.   @override  
  10.   _NewCustomerState createState() => _NewCustomerState();  
  11. }  
  12.   
  13. class _NewCustomerState extends State<NewCustomer> {  
  14.   final GlobalKey<FormState> _formStateKey = GlobalKey<FormState>();  
  15.   String _name;  
  16.   String _age;  
  17.   
  18.   final _nameController = TextEditingController(text: '');  
  19.   final _ageController = TextEditingController(text: '');  
  20.   
  21.   @override  
  22.   Widget build(BuildContext context) {  
  23.     return Scaffold(  
  24.       appBar: AppBar(  
  25.         title: Text("New Customer"),  
  26.       ),  
  27.       body: SingleChildScrollView(  
  28.         scrollDirection: Axis.vertical,  
  29.         child: Column(  
  30.           children: <Widget>[  
  31.             Form(  
  32.               key: _formStateKey,  
  33.               autovalidate: true,  
  34.               child: Column(  
  35.                 children: <Widget>[  
  36.                   Padding(  
  37.                     padding: EdgeInsets.only(left: 10, right: 10, bottom: 5),  
  38.                     child: TextFormField(  
  39.                       onSaved: (value) {  
  40.                         _name = value;  
  41.                       },  
  42.                       controller: _nameController,  
  43.                       decoration: InputDecoration(  
  44.                         focusedBorder: new UnderlineInputBorder(  
  45.                             borderSide: new BorderSide(  
  46.                           width: 2,  
  47.                           style: BorderStyle.solid,  
  48.                         )),  
  49.                         labelText: "Customer Name",  
  50.                         icon: Icon(Icons.account_box, color: Colors.green),  
  51.                         fillColor: Colors.white,  
  52.                         labelStyle: TextStyle(  
  53.                           color: Colors.green,  
  54.                         ),  
  55.                       ),  
  56.                     ),  
  57.                   ),  
  58.                   Padding(  
  59.                     padding: EdgeInsets.only(left: 10, right: 10, bottom: 5),  
  60.                     child: TextFormField(  
  61.                       onSaved: (value) {  
  62.                         _age = value;  
  63.                       },  
  64.                       keyboardType: TextInputType.phone,  
  65.                       inputFormatters: <TextInputFormatter>[  
  66.                         WhitelistingTextInputFormatter.digitsOnly  
  67.                       ],  
  68.                       controller: _ageController,  
  69.                       decoration: InputDecoration(  
  70.                         focusedBorder: new UnderlineInputBorder(  
  71.                             borderSide: new BorderSide(  
  72.                                 color: Colors.green,  
  73.                                 width: 2,  
  74.                                 style: BorderStyle.solid)),  
  75.                         labelText: "Age",  
  76.                         icon: Icon(  
  77.                           Icons.phone_android,  
  78.                           color: Colors.green,  
  79.                         ),  
  80.                         fillColor: Colors.white,  
  81.                         labelStyle: TextStyle(  
  82.                           color: Colors.green,  
  83.                         ),  
  84.                       ),  
  85.                     ),  
  86.                   ),  
  87.                 ],  
  88.               ),  
  89.             ),  
  90.             Divider(),  
  91.             Row(  
  92.               mainAxisAlignment: MainAxisAlignment.center,  
  93.               children: <Widget>[  
  94.                 RaisedButton(  
  95.                   color: Colors.green,  
  96.                   child: Text(  
  97.                     ('SAVE'),  
  98.                     style: TextStyle(color: Colors.white),  
  99.                   ),  
  100.                   onPressed: () {  
  101.                     _formStateKey.currentState.save();  
  102.                     // widget : is used to access property of parent stateful class  
  103.                     widget.customerList.addCustomer(  
  104.                         Customer(name: _name, age: int.parse(_age)));  
  105.                     Navigator.of(context).pop();  
  106.                   },  
  107.                 ),  
  108.               ],  
  109.             )  
  110.           ],  
  111.         ),  
  112.       ),  
  113.     );  
  114.   }  
  115. }  
Step 5
 
Now, in main.dart, we have created a list of customers. Please note that in main.dart file, we have implemented listener and that is the important part of this article so carefully read the comment in the code. It will explain how it works. 
  1. import 'package:flutter/material.dart';  
  2. import 'package:flutter_statemanagement_using_provider/pages/new_customer.dart';  
  3. import 'package:flutter_statemanagement_using_provider/providers/customers.dart';  
  4. import 'package:provider/provider.dart';  
  5.   
  6. void main() => runApp(MyApp());  
  7.   
  8. class MyApp extends StatelessWidget {  
  9.   @override  
  10.   Widget build(BuildContext context) {  
  11.     return MaterialApp(  
  12.       theme: ThemeData(  
  13.         primarySwatch: Colors.blue,  
  14.       ),  
  15.       // We need to place ChangeNotifierProvider just in parent widget where we need to access the data that we have defined in customers.dart and following is the structure of that.  
  16.       home: ChangeNotifierProvider<CustomerList>(  
  17.         // initialized CustomerList constructor with default 1 value.  
  18.         builder: (_) => CustomerList(  
  19.               customers: [  
  20.                 Customer(name: "Parth Patel", age: 30),  
  21.               ],  
  22.             ),  
  23.         child: MyHomePage(title: 'Provider State Management'),  
  24.       ),  
  25.       // Now we are able to access customer data in all the child widgets. for that check below classes  
  26.     );  
  27.   }  
  28. }  
  29.   
  30. class MyHomePage extends StatefulWidget {  
  31.   MyHomePage({Key key, this.title}) : super(key: key);  
  32.   final String title;  
  33.   
  34.   @override  
  35.   _MyHomePageState createState() => _MyHomePageState();  
  36. }  
  37.   
  38. class _MyHomePageState extends State<MyHomePage> {  
  39.   @override  
  40.   Widget build(BuildContext context) {  
  41.     // this is how you can access object of the customer list class  
  42.     // there are 2 ways you can access 1.Provider and 2. Consumer(We will look in another article)  
  43.     final customerList = Provider.of<CustomerList>(context);  
  44.     return Scaffold(  
  45.       appBar: AppBar(  
  46.         title: Text(widget.title),  
  47.       ),  
  48.       body: ListView.builder(  
  49.         itemCount: customerList.getCustomers().length,  
  50.         itemBuilder: (context, index) {  
  51.           return ListTile(  
  52.             title: Text('${customerList.getCustomers()[index].name}'),  
  53.             subtitle: Text('${customerList.getCustomers()[index].age}'),  
  54.             trailing: Container(  
  55.               width: 50,  
  56.               child: Row(  
  57.                 children: <Widget>[  
  58.                   IconButton(  
  59.                     icon: Icon(  
  60.                       Icons.delete,  
  61.                       color: Colors.red,  
  62.                     ),  
  63.                     onPressed: () {  
  64.                       // removed customer  
  65.                       customerList.removeCustomer(index);  
  66.                     },  
  67.                   )  
  68.                 ],  
  69.               ),  
  70.             ),  
  71.           );  
  72.         },  
  73.       ),  
  74.       floatingActionButton: FloatingActionButton(  
  75.         onPressed: () {  
  76.           // Navigated to new customer page and passed object of CustomerList so that page can change data of customer list.  
  77.           Navigator.push(  
  78.             context,  
  79.             MaterialPageRoute(  
  80.                 builder: (context) => NewCustomer(customerList: customerList)),  
  81.           );  
  82.         },  
  83.         child: Icon(Icons.add),  
  84.       ),  
  85.     );  
  86.   }  
  87. }  
Step 6
 
Hurray…. Run the app and test it. :)))
 

Conclusion

 
State Management is one of the key parts of performance improvement of the app and Provider is the best approach to achieve it. Previously, state was managed by scoped models which are not so effective so, it is recommended to use Provider as and when needed.