Starting with Flutter: A simple guide for Provider
Tags: flutter, beginners, tutorial, dart
Published: 2021-05-19 15:06:02

In this tutorial you will learn the basic of the [Provider package](https://pub.dev/packages/provider), we will build a simple app that will change its main color scheme using Provider.

But first, what is Provider?

# What is Provider?

Provider is one of the many state management options when using Flutter. It's one of the first state manager recommended by Flutter itself and one of the simplest. If you're new to state management check out the official pages that describes the different state managers and how/when you should use it https://flutter.dev/docs/development/data-and-backend/state-mgmt/options.

You may ask, what can Provider do? The answer is simple, and the power of the Provider package is in its simplicity:

> Providers allow to not only expose a value, but also create/listen/dispose it.
>
> From: https://pub.dev/packages/provider

When you place a *Provider* widget in your widget tree all the Childs of the Provider will have access to the values exposed by it.
For this tutorial we will use a particular type of Provider: *ChangeNotifierProvider*. It adds the possibility to listen to changes in the provider and it automatically re-builds the widgets when the provider needs to.

# Adding dependencies

The first thing that we need to do is add our dependencies:

```dart
dependencies:
  flutter:
    sdk: flutter
  flutter_colorpicker: ^0.4.0
  provider: ^5.0.0
  cupertino_icons: ^1.0.2
```

# Let's write some code

Then we will need to define our Provider, create a new dart file called `theme_provider.dart` with this content:

```dart
import 'package:flutter/material.dart';

class ThemeProvider extends ChangeNotifier {
  Color mainColor = Colors.blue;

  void changeThemeColor(Color color) {
    mainColor = color;
    notifyListeners();
  }
}
```

 

As you can see our `ThemeProvider` class is simple but has a lot of things that needs explanation:

- It extends [ChangeNotifier](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), a class that "provides change notification to its listeners". So you can listen to an instance of a ChangeNotifier and being notified when it changes.
- It exposes the `mainColor` value which starts as the material blue.
- We will use the `changeThemeColor` function to change the `mainColor`.
- When the `mainColor` changes the class will notify its listener using `notifyListeners().`

Now we will need to add the Provider widget in our widget tree, in our case all the children of the app should have access to the main color of the application, so we sill need to wrap the `App` widget in a `Provider` widget (in particular a `ChangeNotifierProvider` because we're using a ChangeNotifier). `ChangeNotifierProvider` needs a `create` parameter, it's the function that will be called to create our `ThemeProvider`.

```dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ThemeProvider>(
      create: (context) => ThemeProvider(),
      child: Consumer<ThemeProvider>(
        builder: (context, themeProvider, child) => MaterialApp(
          title: 'Flutter Provider Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            appBarTheme: AppBarTheme(brightness: Brightness.dark),
          ),
          home: MainScaffold(),
        ),
      ),
    );
  }
}
```

Now we can access the `mainColor `of our ThemeProvider, to do so we need to use a `Consumer` widget. 

The `Consumer `widget has a builder function that is called whenever the Provider needs to (basically when `notifyListeners` is called). The builder function has 3 parameters:

- context: the context of this build
- themeProvider: our `themeProvided` declared above. If the provider package doesn't find a parent with the correct Provider type it will throw an Exception.
- child: you can optionally pass a child widget to the Consumer, that will be passed to the builder function in this parameter. This is here for optimization, if you have a big widget that doesn't need the value exposed by the provider you can pass it as `child` and use it in the `builder` function.

Here's an example of a text that change color based on the `mainColor` of our `ThemeProvider`:

```dart
Consumer<ThemeProvider>(
              builder: (context, themeProvider, child) => Text(
                'Text',
                style: Theme.of(context).textTheme.headline2?.copyWith(
                      color: themeProvider.mainColor,
                    ),
              ),
            )
```

 

# Show the color picker

How do we change the main color of our app? We will use the [flutter_colorpicker](https://pub.dev/packages/flutter_colorpicker) package that we've added to our dependencies at the beginning, showing it as an alert.

This time we will not need to listen to changes to our `ThemeProvider` class so we can access the provider without a consumer like this.

```dart
ThemeProvider themeProvider = Provider.of<ThemeProvider>(context, listen: false);
```

and then show the color picker:

```dart
  void _showColorPicker(BuildContext context) {
    ThemeProvider themeProvider =
        Provider.of<ThemeProvider>(context, listen: false);
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        titlePadding: const EdgeInsets.all(0.0),
        contentPadding: const EdgeInsets.all(0.0),
        content: Wrap(
          children: [
            ColorPicker(
              pickerColor: themeProvider.mainColor,
              onColorChanged: (color) => themeProvider.changeThemeColor(color),
            ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: Text('Close'),
          )
        ],
      ),
    );
  }
```

As you can see when the color changes (`onColorChange`) we're calling the `changeThemeColor` of our provider that will change the color and trigger a rebuild with `notifyListeners`.

# Why don't you just call setState?

The obvious question that could come to your mind is "Why don't you just use a StatefulWidget with setState?". Here are some of the reasons:

- One of the main reasons to prefer `Provider` over `Statefulwidget`s is that, using `Provider`, you will rebuild only the widgets that needs that value (the `Consumers`) while the other will not be rebuilt. Instead when you call `setState` the whole build function of the widget will be called.
-  You can use the values exposed by providers in other widgets without passing them. In our case, if you need to push a new Scaffold you can still use the mainColor with the Consumer because it will be a child of the Provider
- You can have different Providers, that do different things using a [MultiProvider](https://pub.dev/packages/provider#multiprovider)
- The allocation and disposal of objects is managed by the package and the objects are created lazily when they are needed.

# Wrap Up

Here's a complete example of a `Scaffold` that uses the Theme:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:provider/provider.dart';
import 'package:theme_provider/theme_provider.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _ThemedAppBar(
        title: Text('Theme provider example'),
        actions: [
          IconButton(
            icon: Icon(Icons.colorize),
            onPressed: () => _showColorPicker(context),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Consumer<ThemeProvider>(
              builder: (context, themeProvider, child) => Text(
                'Text',
                style: Theme.of(context).textTheme.headline2?.copyWith(
                      color: themeProvider.mainColor,
                    ),
              ),
            ),
            Consumer<ThemeProvider>(
              builder: (context, themeProvider, child) => Text(
                'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
                style: Theme.of(context).textTheme.bodyText2?.copyWith(
                      color: themeProvider.mainColor,
                    ),
              ),
            ),
            Consumer<ThemeProvider>(
              builder: (context, themeProvider, child) => Container(
                height: 100,
                width: 100,
                decoration: BoxDecoration(
                  color: themeProvider.mainColor,
                  borderRadius: BorderRadius.all(
                    Radius.circular(50),
                  ),
                ),
              ),
            ),
            Consumer<ThemeProvider>(
              builder: (context, themeProvider, child) => Slider(
                activeColor: themeProvider.mainColor,
                inactiveColor: themeProvider.mainColor.withOpacity(0.5),
                value: 0,
                onChanged: (newValue) {},
              ),
            ),
            Consumer<ThemeProvider>(
              builder: (context, themeProvider, child) => Switch(
                activeColor: themeProvider.mainColor,
                value: true,
                onChanged: (newValue) {},
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _showColorPicker(BuildContext context) {
    ThemeProvider themeProvider =
        Provider.of<ThemeProvider>(context, listen: false);
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        titlePadding: const EdgeInsets.all(0.0),
        contentPadding: const EdgeInsets.all(0.0),
        content: Wrap(
          children: [
            ColorPicker(
              pickerColor: themeProvider.mainColor,
              onColorChanged: (color) => themeProvider.changeThemeColor(color),
            ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: Text('Close'),
          )
        ],
      ),
    );
  }
}

class _ThemedAppBar extends StatelessWidget with PreferredSizeWidget {
  final Widget? title;
  final List<Widget>? actions;

  final Size preferredSize;

  _ThemedAppBar({
    Key? key,
    this.title,
    this.actions,
  })  : preferredSize = Size.fromHeight(kToolbarHeight),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeProvider>(
      builder: (context, themeProvider, child) => AppBar(
        title: title,
        actions: actions,
        backgroundColor: themeProvider.mainColor,
      ),
    );
  }
}

```


> To build an AppBar with the backgroundColor as our mainColor we had to build a custom widget (the _ThemedAppBar that you see at the bottom) because the Scaffold AppBar needs to be a PreferredSizeWidget so we couldn't use the usual Consumer.



![Wrapping all up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1smfi5ytgtln495wud3.gif)

Article written by the awesome [Lorenzo](https://www.linkedin.com/in/lorenzo-oliveto-638620153/)