Flutter State Management made easy with provider | Flutter Tutorial for Beginners
Tags: flutter, mobile, android, ios
Published: 2022-04-27 15:09:52

In this article I am going to teach you in an extremely simple and short way how to apply state management in a Flutter app with the [provider](https://pub.dev/packages/provider) package.

> 📽 Video version available on [YouTube](https://youtu.be/nqJl5xgRW3A) and [Odysee](https://odysee.com/@svprdga:d/flutter-state-management-made-easy-with-provider-flutter-tutorial-for-beginners)

{% embed https://youtu.be/nqJl5xgRW3A %}

The resulting application is going to be a counter app in which we can increase the counter if we press the button located at the bottom of the screen:

![counter_app_blog.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650992727122/Qf3XMxsbT.png)

Does this example sound familiar to you? Yes, what we are going to do is refactor the app template that Flutter creates when you create a project, leaving the functionality and the visual part intact, but refactoring its state management with provider.

### Provider architecture

Before we start I'm going to show you the change we're going to make. The default state management that Flutter gives you when you create a project is as follows:

![classic_widget_blog.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650993598448/kkiQczCo0.png)

In this diagram we see a widget that is responsible for the visual part and state management. This management is done through variables in the body of the class, when we want to change the state we use the `setState()` method.

The change that we are going to apply consists of refactoring this state management so that it looks like this:

![reactive_pattern_blog.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650993618606/JfEhAxUsb.png)

On the left side we have a model class that will contain the variables that will constitute the state of our widget, this widget will subscribe to this model to receive state changes, and when those changes are made the model will emit the updated values to the widget. By doing so we are creating a circular reactive architecture.

A great advantage of this model is that we have a clear separation between the state logic and the display of views, thus achieving a much cleaner code that is easier to organize and easier to maintain.

In addition, with this methodology we could also achieve the following:

![multiple_listeners_blog.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1650993623716/U4qIfZ4uQ.png)

In this diagram you can see how not just one, but multiple widgets can subscribe to the model class for all of them to receive state changes. A practical example of this would be for example a class that manages user authentication against a server, you could have a single class that contains methods like `login()`, `register()`, `getUser()`... and then several widgets that consume these changes and that are automatically modified when a state change occurs due to user authentication.

### Create the model that manages the state

First, create an application to work on:

`flutter create state_management_provider`

Open the created app in your trusted IDE and modify `pubspec.yaml` to include the provider dependency:

```yaml
name: state_management_provider
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ">=2.16.2 <3.0.0"

dependencies:
  flutter:
    sdk:flutter
  cupertino_icons: ^1.0.2
  provider: ^6.0.2

dev_dependencies:
  flutter_test:
    sdk:flutter
  flutter_lints: ^1.0.0

flutter:
  uses-material-design: true
```

Download the dependencies by running `flutter pub get` or through your IDE.

Now if we go to `lib/main.dart` we will see that the state is being managed with a `_counter` variable. We are going to create a new class in a file called `main_model.dart` that extends `ChangeNotifier`, by extending this class it will allow other classes to subscribe to it and receive state changes when calling the `notifyListeners()` method. Let's move the `_counter` variable into this class and add a getter and a setter to it as follows:

```dart
// lib/main_model.dart

import 'package:flutter/material.dart';

class MainModel extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  set counter(int value) {
    if (value != _counter) {
      _counter = value;
      notifyListeners();
    }
  }
}
```

Here you can see that we still have a private variable `_counter` initialized with the value _0_. Through its getter (marked with the keyword `get`) the widgets can obtain this value at any time. Through its setter (marked with the keyword `set`) the widgets can update their value, and when this happens first we will check that the new value is different from the previous one, if it is, we update the private variable that stores the real value and invoke the `notifyListeners()` method so that all widgets that have subscribed to this class are notified so their view can be re-rendered with the new value.

### Refactor the view so it consumes the model

We are now going to apply changes to `main.dart` so that we can use the model class we just created. First of all, remove all comments and also the current state management in this class:

```dart
// lib/main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

At this point the IDE will highlight two errors, this is because we have removed the `_counter` variable and the `_incrementCounter` method.

In order to be able to use the model class that we have created, we first need a widget that provides it to the entire widget tree that hangs from it, and that widget is called `ChangeNotifierProvider`, included in the `provider` package. This component allows us to create a class that extends `ChangeNotifier` so that any widget under it can get it.

Also, in order to react to changes, we're going to need a widget that can read those changes and react. This widget is called `Consumer`.

The combination of both components look like this:

```dart
    ChangeNotifierProvider<MainModel>(
      create: (_) => MainModel(),
      child: Consumer<MainModel>(
        builder: (context, model, child) {

          // Here you can use the model variable 
          // to read and alter the state
          
        },
      ),
    )
```

We are going to modify the `build()` method with all of the above and we are going to use the `model` variable that the `Consumer` gives us to read the current value of the counter and also to modify it:

```dart
@override
Widget build(BuildContext context) {
  return ChangeNotifierProvider(
    create: (_) => MainModel(),
    child: Consumer<MainModel>(
      builder: (context, model, child) => Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '${model.counter}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => model.counter++,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
    ),
  );
}
```

Note that in this case we don't need the method we used before to modify the state: `setState()`. Now we only need to set the new value and the model class will take care of propagating this change, the `Consumer` will receive this change and will re-draw our UI.

### Conclusion

In this small example we have implemented state management with provider in the simplest and most basic way possible. The provider package offers many more possibilities and resources to do more advanced things, but we can leave that for another day.

If you have found this article useful and you would like me to do more tutorials explaining more in-depth state management with provider and other possible applications, please let me know by writing below in the comment box.

You can find the final app of this tutorial [here](https://github.com/svprdga/Flutter-State-Management-Basic-Provider).

Thanks for reading this far and happy coding :)