What is state management and provider in Flutter

Share

We use the Stateful widget and the function setState() to change the state in an application with limited interaction with outside data sources. However, if our application is highly interactive and the widgets require a change of state more often, then it might be a hassle to call setState() in every changing widget. Provider in Flutter is there to help us change states in an efficient way, avoiding the tediousness of calling setState() everywhere we want a change.

Provider in Flutter

There are two terms that handle state management in provider:

  • ChangeNotifier
  • ChangeNotifierProvider

ChangeNotifier is used to notify changes to multiple listeners. It calls notifyListeners() in every setter function so every listener is aware of the change. Upon listening to a change, the listeners perform a certain action.

The code below shows the class CountingTheNumber manages changes for the variable number. When the variable is incremented, notifyListeners() is called to make the change known to the listeners.

import 'package:flutter/widgets.dart';
class CountingTheNumber with ChangeNotifier {
int number = 0;
void increaseNumber() {
number++;
notifyListeners();
}
}

ChangeNotifierProvider() is a wrapper around ChangeNotifier. It allows us to directly communicate the changed state with the lower widgets without modifying the top ones.

In the main, the model and app need to be wrapped in ChangeNotifierProvider(), as shown below. The root of our application is also shown:

import 'package:basic_flutter_provider/models/counting_the_number.dart';
import 'package:basic_flutter_provider/views/my_home_page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CountingTheNumber(),
child: 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: MyHomePage(),
);
}
}

The ChangeNotifierProvider() is most helpful when our widget tree is very deep. So, let us make a very deep widget tree to fully understand the importance of ChangeNotifierProvider().

import 'package:basic_flutter_provider/controllers/a_very_deep_widget_tree.dart';
import 'package:flutter/material.dart';
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Basic Provider Explained to Beginners'),
),
body: Center(
child: AVeryDeepWidgetTree(),
// This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}

Let’s assume we have a very deep nested widget tree, as shown below.

import 'package:basic_flutter_provider/models/counting_the_number.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AVeryDeepWidgetTree extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ‘Provider.of’, just like Consumer needs to know the type of the model.
//We need to specify the model ‘CountingTheNumber’.
final counter = Provider.of<CountingTheNumber>(context);
return Container(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'This is a simple Text widget',
style: TextStyle(
color: Colors.black,
fontSize: 45.0,
fontWeight: FontWeight.bold,
),
),
//now we are going to build a very deep widget tree
Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'This is another simple Text widget deep inside the tree.',
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 5.0,
),
Text(
'You have pushed the button this many times:',
style: TextStyle(fontSize: 35.0),
),
SizedBox(
height: 5.0,
),
Text(
'${counter.number}',
style: TextStyle(fontSize: 25.0),
),
SizedBox(
height: 5.0,
),
FloatingActionButton(
onPressed: () {
counter.increaseNumber();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
],
),
),
),
],
),
);
}
}

Line 10 gives access of the model CountingTheNumber anywhere in the widget tree. This is done using Provider.of().

final counter = Provider.of<CountingTheNumber>(context);

The pressed button should only change the text for the number on the screen. So, only the build text and FloatingActionButton should be rebuilt by pressing the button.

The code below is the only part that should be rebuilt upon pressing the button.

Text(
'${counter.number}',
style: TextStyle(fontSize: 25.0),
),
SizedBox(
height: 5.0,
),
FloatingActionButton(
onPressed: () {
counter.increaseNumber();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),

Due to the wrapper ChangeNotifierProvider(), we can do this by separating the part that needs to be rebuilt. In this way, we will be able to rebuild that part independently without building the parent widgets.

So, below is a new widget, ColumnClass, containing only the part that needs to be rebuilt.

import 'package:flutter/material.dart';
import 'package:flutter_provider_explained_for_beginners/model/counting_the_number.dart';
import 'package:provider/provider.dart';
class ColumnClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ‘Provider.of’, just like Consumer needs to know the type of the model.
// We need to specify the model ‘CountingTheNumber’.
//this time only this widget will be rebuilt
final CountingTheNumber counter = Provider.of<CountingTheNumber>(context);
return Column(
children: [
Text(
'${counter.number}',
style: TextStyle(fontSize: 25.0),
),
SizedBox(height: 10.0),
FloatingActionButton(
onPressed: () {
counter.increaseNumber();
},
tooltip: 'Increment',
child: Icon(Icons.add),
)
],
);
}
}

The last thing to do is to add the ColumnClass() to the AVeryDeepWidgetTree widget.

import 'package:basic_flutter_provider/controllers/a_very_deep_widget_tree.dart';
import 'package:flutter/material.dart';
class AVeryDeepWidgetTree extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'This is a simple Text widget',
style: TextStyle(
color: Colors.black,
fontSize: 45.0,
fontWeight: FontWeight.bold,
),
),
//now we are going to build a very deep widget tree
Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'This is another simple Text widget deep inside the tree.',
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 5.0,
),
Text(
'You have pushed the button this many times:',
style: TextStyle(fontSize: 35.0),
),
SizedBox(
height: 5.0,
),
// the whole top widgets will remain unaffected when state changes
ColumnClass(),
],
),
),
),
],
),
);
}
}

Now, upon pressing the button only ColumnClass() will be rebuilt.

Summary

The state management through Provider allows us to update the state in an efficient manner. This prevents us from the trouble of calling setState() multiple times and building the parent widgets.

Copyright ©2024 Educative, Inc. All rights reserved