Provider is the most popular and Flutters Favorite and recommended package for managing the State in Flutter applications. This is basically a wrapper around InheritedWidget which is normally used to manage the state of an application. In large and complex applications it can be difficult to manage the state using this Inherited Widget. It can be easier to use the Provider package in such scenarios instead of inherited widget.

State of an Application

Before checking what the Provider package offers to us, first we need to understand the State of an Application.

Whether it is Flutter or React JS or Angular, the state of an application refers to the Data inside the application at one particular point of time. Now, as these frameworks or widget/component based, the data is distributed through out the application in different widgets.

This state inside the application can change over a period of time. The most common things that causes the change are the actions taken by the user inside the application or the data received from the server.

Suppose a user logs into an application which triggers a change in the state such as changing user type from ‘guest’ to ‘registered’ or any other. Now this information may be needed at different parts inside the application. In one widget we may want to show a welcome message, in another widget his profile, in another widget his orders and so on. If we have all these widgets in one page/widget, then, we can manage somehow by using setState. But most of the times the widgets that needs the data will be in different pages and different widgets.

Passing data through constructors from one widget to another can become a quite tedious thing even with few numbers of state variables.

State Management

There are different tools and different approaches to manage the state of an application in an efficient and easier way. Provider is the most popular state management tool for Flutter. There are other ways such as bloc pattern, hooks etc.,

Provider

In the provider way of managing the state of an application, we separate the state (data) and logic that changes the state (functions) from the widgets and keep it in a separate class usually called as provider . We then make this provider available / injected to a top level widget in the application tree. All the widgets which are down the tree from this widget can now access the data and logic from the provider directly.

The data passed from the provider to the widgets below the application tree can be accessed in different ways.

  • context.read<T>() : using this syntax, we can read the data from the T provider. Any changes made to this data will not be notified to the widget. This is like read-only. Equivalent syntax similar to inherited widgets is Provider.of<T>(context, listen: false )
  • context.watch<T>() : with this syntax, we can read and watch the data inside the provider T. Any changes done to the data inside the provider can be informed to the widgets by using notifyListners method. Equivalent inherited widget syntax is Provider.of(T)(context, listen: true)
  • context.select<T, R>: using this syntax we can listen to a selective part of T

The Provider package provides different types of providers to handle different types of state management.

Provider

If you have some static data that you want to use through out the application then you can use this provider. Since this provider is used to provide static data, any changes to the data (we should not let even this to happen if the data is static by using final/ const fields) will not be notified to the widgets using this data, thus, it will not be reflected in those widgets.

example:

You can have your data provider like this

import 'package:flutter/material.dart';

class DataProvider {
  final String imgSource = 'https://example.com/assets/images/';
}

then provide this in main.dart file

void main() {
  runApp(
      Provider(create: (context) => DataProvider(),child: MyApp(), )
  );
}

Now, we can use this data anywhere in the application in any widget that are under the MyApp widget tree.

 late DataProvider dataProvider;
  @override
  void initState() {
    dataProvider = context.read<DataProvider>();
    print(dataProvider.imgSource); // we get https://example.com/assets/images/
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            const Text('Image Gallery'),
            Image.network(dataProvider.imgSource+'img.png')
          ],
        ),
      ),
    );
  }

Here we used context.read<DataProvider>() to read the data from DataProvider, instead of that we can also use Provider.of<DataProvider>(context, listen: false ).