Search⌘ K
AI Features

The Provider Library Fundamentals

Explore how to manage state in Flutter apps using the Provider library. Learn to set up ChangeNotifierProvider, use models for state changes, and minimize widget rebuilds in complex widget trees for better app performance.

What is the Provider package

What is the Provider in Flutter? The Provider is a Flutter package. It’s a wrapper around the InheritedWidget.

Before we proceed, we must add the dependency on the provider to the pubspec.yaml file.

It looks like this:

YAML
dependencies:
flutter:
sdk: flutter
provider:

Now you are set to use the provider to manage a state in your app.

What is a state? The state is something that exists in memory. When the app is running, in most cases, we will try to manage the state. The Provider helps you to do that.

The state itself is critical, while efficiently managing it is essential too.

That’s because managing state is crucial to building any complex app that handles multiple screens, different variables, and user sessions. Hence, you should plan it.

Flutter creates a deep and nested widget tree. To manage a state at the lowest bottom, we cannot rebuild every top widget. That is wasteful memory consumption.

So, how can we solve this problem? The Provider package in Flutter is the answer!

Now let us do some coding together. Our goal is to understand the main concept of Flutter Provider.

Using ChangeNotifierProvider is one solution. It comes from the provider package, and it provides an instance of a ChangeNotifier to the widgets. So now Flutter has in-built mechanisms for widgets to provide data and services to their distant descendants.

Widget is very powerful. ChangeNotifierProvider uses that power to notify the listeners.

How does it do that?

By making it possible to place it on top of our widget tree.

Dart
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(),
);
}
}

It creates a context that returns the model from where the data comes from, and it also returns a child widget, which will consume the data.

Why do we need a model?

We need it for a simple reason. First of all, it will store and give us changed data. Although the data is temporary or short-lived in this example, we still need a small model class.

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

Now we can provide this designed model to the desired widget. However, our desired widget positions itself at the lowest point.

Dart
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.
),
);
}
}

While sending our data directly to the lowest point, we shouldn’t rebuild the top widgets.

The AVeryDeepWidgetTree class is a long code snippet.

Let us now understand how it works. Hint: It’s something to do with a deeply nested widget tree.

As explained earlier, Flutter allows nested widget trees. We cannot imagine a complex app without that.

Here is the snapshot of the app we are trying to build:

Let us see the code now. Then I’ll explain what is happening.

Dart
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),
),
],
),
),
),
],
),
);
}
}

Remember our model class. We have only one variable that will reflect the state change. So at the bottom-most widget, we need to access it.

We will use the Provider.of() method, where we will mention the type of our model and pass the context.

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

The Provider helps us to access the model data and method anywhere inside that widget tree.

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

When we click the button, it will change the state of the app by increasing the number.

The main drawback of the above code hides in this line of Code:

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

We want to rebuild only the text part where the number will show itself, and the floating action button section.

To achieve that, all we need to do is write a separate widget for those parts. After that, we will call it inside our view.

Dart
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),
)
],
);
}
}

Next, we will add the following widget to the AVeryDeepWidgetTree:

Dart
// the whole top widgets will remain unaffected when state changes
ColumnClass(),

That’s all. When we press the button and change the state, running the code will only change a tiny segment of the widget.

We can test this in the code widget given below:

name: basic_flutter_provider
description: A new Flutter project.
###Clear1
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  provider:


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages
Flutter basic provider app

We have successfully removed the pitfalls of rebuilding the whole widget tree. Instead of changing the state for the whole tree, we only change the state for that widget section. It does not affect the parent widgets and avoids widget rebuilding.

In any ideal Flutter App, it is better to avoid widget rebuilding, as much as possible. The advantage of the Provider or Riverpod package is that they help us avoid widget rebuilding.