[Flutter] Provider

2021-05-27 hit count image

In this blog post, I will show you how to use Provider to use a global state or share the state between widgets in Flutter.

Outline

Int this blog post, I will introduce how to use Provider to manage the global state or share the state between widgets in Flutter.

You can see the full source code of the blog post on GitHub.

So, let’s see how to use Provider in Flutter to manage the global state and share the state between widgets.

Create Flutter project

To use the flutter_provider package, execute the command below to create new Flutter project.

flutter create provider_example

To apply Null safety, execute the command below.

cd provider_example
dart migrate --apply-changes

Install flutter_provider package

To share the global state and share the state between widgets, execute the command below to install the flutter_provider package.

flutter pub add flutter_provider

What is Provider

There are two types of the widgets. One is the Stateless Widget which has no state, and another is the Stateful Widget which has the state.

The Statefule Widget has the state(data) and when the state is changed, the UI is changed by the state.

flutter state

If the other widgets need the same state(data), what can we do?

flutter state required

Change the two widgets’ shared parent widget to the Stateful Widget, and by passing the state to the child widget when you create the child widget, you can share the state between two widgets.

flutter state required

However, to show the state, many widgets do re-build unnecessarily, so the performance issue may occur. To solve this issue, Provider is created. When we want to share the state between wigets globally, we use Provider.

flutter provider

When we use Provider, we create a class to store the state(data) regardless of the widget tree, and provider Provder to the shared parent widget, and read Provider data in the widget which needs to consume the state.

How to use

Now, let’s see how to use the flutter_provider package to manage the global state. In this blog post, we will make a simple counter app which uses the flutter_provider package.

flutter provider

When you press the + button, the counter is increased, and when you press the - button, the number is decreased. It’s very simple app, so let’s see how to use Provider in Flutter via this app.

Provider

First, let’s create a Provider to manage the global state. Create the lib/providers/counts.dart file and modify it like below.

import 'package:flutter/material.dart';

class Counts with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }

  void remove() {
    _count--;
    notifyListeners();
  }
}

To use Provider, we need to create a class with ChangeNotifier.

import 'package:flutter/material.dart';

class Counts with ChangeNotifier {
  ...
}

And then, define a state variable to share in the app. Also, create the getter to access the variable.

import 'package:flutter/material.dart';

class Counts with ChangeNotifier {
  int _count = 0;
  int get count => _count;
  ...
}

And then, create functions to change the state. In here, I’ve created the add function to increase the value and the remove function to decrease the value.

class Counts with ChangeNotifier {
  ...
  void add() {
    _count++;
    notifyListeners();
  }

  void remove() {
    _count--;
    notifyListeners();
  }
}

Important thing is when the value is changed, we should call the notifyListeners() function to notify the value is changed to the widgets. It’s like to change the state value in the Stateful Widget, we use the setState function to notifiy the state is changed to the widget. If we don’t call the notifyListeners function, the other widgets can’t recognize the value is changed.

Done! we’ve created a Provider to make a global state in the app.

Main

Next, let’s provider Provider to the shared parent widget of the widgets that use the global state. Open the lib/main.dart file and modify it like below.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
import 'package:provider_example/widgets/buttons.dart';
import 'package:provider_example/widgets/counter.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counts()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider'),
      ),
      body: ChangeNotifierProvider(
        create: (BuildContext context) => Counts(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Counter(),
              Buttons(),
            ],
          ),
        ),
      ),
    );
  }
}

To use the global state, import the flutter_proivder package and the state class that we’ve created.

...
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
import 'package:provider_example/widgets/buttons.dart';
import 'package:provider_example/widgets/counter.dart';
...

Import the widgets which use Provider(we didnt’ create it yet.)

...
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counts()),
      ],
      child: MyApp(),
    ),
  );
}
...

In this example, I added Provider to the top widget of the widget tree. Also, when we develop the app normally, we use multiple Providers, so, I used MultiProvider to be able to use multiple Providers.

...
class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider'),
      ),
      body: ChangeNotifierProvider(
        create: (BuildContext context) => Counts(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Counter(),
              Buttons(),
            ],
          ),
        ),
      ),
    );
  }
}
...

After that, the screen is configured in the way of developing an ordinary app. Next, let’s develop the Counter and Buttons widgets to use Provider.

Counter

Let’s create the widget which uses Provider. Create the lib/widgets/counter.dart file and modify it like below.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';

class Counter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Counter');

    return Text(
      context.watch<Counts>().count.toString(),
      style: TextStyle(
        fontSize: 20,
      ),
    );
  }
}

The Counter is a simple widget that uses the Text widget to show the counter in the screen. At this widget, I used context.watch<Counts>().count to watch the count value of Provider. So, when the value is changed, the value in the screen will be changed.

Buttons

Next, to change the Provider value, let’s create the Buttons widget. Create the lib/widgets/buttons.dart file and modify it like below.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';

class Buttons extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
            onPressed: () {
              context.read<Counts>().add();
            },
            child: Icon(Icons.add)),
        SizedBox(
          width: 40,
        ),
        ElevatedButton(
            onPressed: () {
              context.read<Counts>().remove();
            },
            child: Icon(Icons.remove))
      ],
    );
  }
}

Unlike the Counter widget, The Buttons widget uses context.read<Counts>() to call the add and remove functions to change the count value in Provider.

When the add and remove functions are called in the Buttons widget, the Provider changes the values, and calls the notifyListeners() function to notify the value is changed. After changing the value like this, the widgets, which use context.watch or context.select to use Provider’s value, is re-built and shown up with the new value.

watch, read, select

Provider provides watch, read, select features.

  • read: the widget reads the state, but doesn’t watch the changes.
  • watch: the widget watches the state changes.
  • select: the widget watches a part of the state.

Normally, we use the read to access the function to change the Provider’s state value, and use the watch to use the state value. To show the changed state value, the re-build occurs, but this re-build is high costs. So, like below, we can optimize re-build to use select to watch a part of the state.

Widget build(BuildContext context) {
  final name = context.select((Person p) => p.name);
  return Text(name);
}

Execute

Execute the command below to star the app which we’ve created.

flutter run

Or, when you can execute the app via your editor’s debug feature, you can see the screen lik below.

flutter provider

And then, when you press the + button on the screen, you can see the counter is increased. Also, when you press the - button, you can see the number is decreased.

Completed

Done! we’ve see how to use Provider to manage the global state in Flutter. Also, we’ve created a simple app to understand how to use Provider.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

Posts