Chinedu Imoh Chinedu is a tech enthusiast focused on full-stack JavaScript and Infrastructure engineering.

A quick guide to Provider for Flutter state management

4 min read 1297

The concept of state management remains one of the most critical topics in Flutter. This is because everything we do in Flutter, from operations related to receiving information from a user to displaying a piece of data, deals with the state. Therefore, managing this data in the best way possible ensures the application is clean-coded, properly abstracted, operates smoothly, and delivers the best results possible.

Many state management solutions have been developed over the years, each based on the same concept of manipulating or modifying the state in the cleanest and most easily accessible way possible. In this article, we will be building a sample app with one of the best state management packages for Flutter: Provider.

Before we begin, note that this article assumes you have an operational Flutter development environment on your machine, along with working knowledge of Flutter.

Let’s talk about what it means to manage the state in a Flutter application.

What is the state in Flutter?

The “state” in Flutter refers to the data stored inside a widget that can be modified depending on the current operation. The state of an app can be updated or completely changed at the start of an application, or when a page reloads.

That means everything widgets do requires handling the data retrieved from the user and passing it among themselves to perform one or more operations. Flutter can also use the state to display pieces of information to the user.

What is Provider?

The Provider package, created by Remi Rousselet, aims to handle the state as cleanly as possible. In Provider, widgets listen to changes in the state and update as soon as they are notified.

Therefore, instead of the entire widget tree rebuilding when there is a state change, only the affected widget is changed, thus reducing the amount of work and making the app run faster and more smoothly.

State management with Provider

Recall what we discussed about Provider earlier: that widgets listen to changes and notify each other if there is a rebuild. As soon as the state changes, that particular widget rebuilds without affecting other widgets in the tree.

Three major components make all of this possible: the ChangeNotifier class in Flutter, the ChangeNotifierProvider (primarily used in our sample app), and the Consumer widgets.

Whatever change in the state observed from the ChangeNotifier class causes the listening widget to rebuild. The Provider package offers different types of providers – listed below are some of them:

  • The Provider class takes a value and exposes it, regardless of the value type
  • ListenableProvider is the specific provider used for listenable objects. It will listen, then ask widgets depending on it and affected by the state change to rebuild any time the listener is called
  • ChangeNotifierProvider is similar to ListenableProvider but for ChangeNotifier objects, and calls ChangeNotifier.dispose automatically when needed
  • ValueListenableProvider listens to a ValueListenable and exposes the value
  • StreamProvider listens to a stream, exposes the latest value emitted, and asks widgets dependent on the stream to rebuild
  • FutureProvider takes a Future class and updates the widgets depending on it when the future is completed

Getting started

Start by creating a new project and add this line to the dependencies block in your pubspec.yaml file:

dependencies:
 provider: ^5.0.0

Run the pub get command to get a local copy of the package:

flutter pub get

Next, we need to create a new Material app in the main.dart file:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
  title: 'Material App',
  home: Scaffold(
  appBar: AppBar(
   title: Text('Material App Bar'),
  ),
  body: Center(
   child: Container(
   child: Text('Hello World'),
   ),
  ),
  ),
 );
 }
}

Managing state data

Now, create a new class that contains the state data required for the application. Let’s name it UserDetailsProvider. The UserDetailsProvider class will declare all the methods dealing with handling the state here.

This class extends the ChangeNotifier class; ChangeNotifier provides us access to the notifyListeners method, which we will use to notify listening widgets to rebuild when the state changes.

We declare two controllers for our TextFormField: name and age. The method for updating the name and age of the user based on user input is also declared in this class.

Everything dealing with the state of the app is declared here:

class UserDetailsProvider extends ChangeNotifier {
 TextEditingController nameController = TextEditingController();
 TextEditingController ageController = TextEditingController();
 int _age = 0;
 String _userName = '';
 int get userAge => _age;
 String get userName => _userName;
 void updateAge(int age) {
 _age = age;
 notifyListeners();
 }
 void updateName(String name) {
 _userName = name;
 notifyListeners();
 }
}

Updating the state

After the name is updated, we call the notifyListeners method, which informs the listening widgets about a change in the state and, therefore, triggers a rebuild of all relevant widgets.

Now that we have the UserDetailsProvider class (which handles the state), we need to link the class to the screen by using ChangeNotifierProvider. Now, wrap the entire app with a ChangeNotifierProvider in the runApp method of the main block.

The ChangeNotifierProvider exposes two important properties: create and child. The class we declared, which extends ChangeNotifier, is passed into the create property, linking the class to the screen:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(
  ChangeNotifierProvider<UserDetailsProvider>(
  create: (_) => UserDetailsProvider(),
  child: MyApp(),
  ),
 );
// ignore: use_key_in_widget_constructors
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return const MaterialApp(
  title: 'Material App',
  home: HomeScreen(),
 );
 }
}

Now the app is linked to the class providing the state; whenever there is a change in state, it causes a rebuild of the screens in-app.



Gathering user data

Currently, the HomeScreen widget contains a form with two TextFormFields to receive the name and age of the user. Also, a RawMaterialButton is included to save changes after the user has passed in the required details.

After this set of widgets, we have two Text widgets that display the values given by the user. These two widgets are the only widgets that need to be updated whenever there is a change in the application state.

That means we do not need every screen to rebuild every time there is a change in the state. Therefore, we need a way to selectively rebuild only the Text widgets concerned with the state change. For that, we have the Consumer widget.

Selectively updating the state

The Consumer widget allows only the child widgets to rebuild without affecting other widgets in the widget tree. As stated previously, we want only the text widgets displaying the details given by the user to update.

We achieve this by wrapping the two Text widgets with a Column and returning it at the builder function exposed by the Consumer widget:

Consumer<UserDetailsProvider>(
  builder: (context, provider, child) {
  return Column(
   children: [
   Text(
    'Hi ' + provider.userName,
    style: const TextStyle(
    fontSize: 18,
    fontWeight: FontWeight.bold,
    ),
   ),
   Text(
    'You are ' + provider.userAge.toString() + ' years old',
    style: const TextStyle(
    fontSize: 18,
    fontWeight: FontWeight.w400,
    ),
   ),
   ],
  );
  },
 ),

Now, only the Text widgets will update whenever the state changes in the app.

Be sure to use the providers at the lowest level possible; you can use the providers only with the widgets affected. Using it at a high level will cause widgets not concerned with the state change to rebuild. Same thing with the Consumer widget; make sure you consume at the specific level to avoid rebuilding the entire widget tree.

Our sample app is finally ready!


More great articles from LogRocket:


Conclusion

Emphasis on the importance of state management in Flutter cannot be overstated. Today, we have dissected the Provider package and used it to manage the state of a sample Flutter application. Hopefully, with the hands-on knowledge you have gained by building an app alongside this article, you can now correctly manage the state of your app in a clean and more accessible manner.

Get setup with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Chinedu Imoh Chinedu is a tech enthusiast focused on full-stack JavaScript and Infrastructure engineering.

Leave a Reply