Open In App

Flutter - Provider Package

Last Updated : 25 Apr, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

The provider package is an easy to use package which is basically a wrapper around the InheritedWidgets that makes it easier to use and manage. It provides a state management technique that is used for managing a piece of data around the app.

The basic classes available in the provider package are -

  • ChangeNotifierProvider<T extends ChangeNotifier>: It listens to a ChangeNotifier extended by the model class, exposes it to its children and descendants, and rebuilds its dependencies whenever notifyListeners is called.
ChangeNotifierProvider(
create: (context) => DataModel(),
child: ...
)
  • Consumer<T>: It obtains the provider from its ancestors and passes the value obtained to the builder.
@override
Widget build(BuildContext context) {
return Consumer<DataModel>(
builder:
(context, data, child) => Column(children: [child!, Text(data.first)]),
child: Text('Static widget'), // This stays constant
);
}
  • FutureProvider<T>: This class listens for a Future and then passes its values to its children and descendants.
Constructors
FutureProvider<T>(
{Key key,
@required Create<Future<T>> create,
T initialData,
ErrorBuilder<T> catchError,
UpdateShouldNotify<T> updateShouldNotify,
bool lazy,
TransitionBuilder builder,
Widget child}
)
This creates a Future from create and subscribes to it.

FutureProvider.value(
{Key key,
@required Future<T> value,
T initialData,
ErrorBuilder<T> catchError,
UpdateShouldNotify<T> updateShouldNotify,
TransitionBuilder builder,
Widget child}
)
This constructor notifies the changed values to the FutureProvider children.

Ex: FutureProvider<Model>(create: (context) =>
Model(),)
  • InheritedProvider<T> The InheritedProvider provides a general implementation of the InheritedWidget.
  • MultiProvider A provider that is used to provide more than one class at the same time.
MultiProvider(
providers: [
Provider<Model1>(create: (context) => Model1()),
StreamProvider<Model2>(create: (context) => Model2()),
FutureProvider<Model3>(create: (context) => Model3()),
],
child: someWidget,
)
  • Provider<T> It is the basic provider.
  • ProxyProvider<T, R> This provider depends on other providers for value. The value can be used by create or update.
Constructor
ProxyProvider(
{Key key,
Create<R> create,
@required ProxyProviderBuilder<T, R> update,
UpdateShouldNotify<R> updateShouldNotify,
Dispose<R> dispose, bool lazy,
TransitionBuilder builder,
Widget child}
)
This initializes key for subclasses.
  • StreamProvider<T> This class listens for a Stream and then passes its values to its children and descendants. This can be used as
Constructors
StreamProvider<T>(
{Key key,
@required Create<Stream<T>> create,
T initialData,
ErrorBuilder<T> catchError,
UpdateShouldNotify<T> updateShouldNotify,
bool lazy,
TransitionBuilder builder,
Widget child}
)
This creates a stream using create and subscribes to it.

StreamProvider.value(
{Key key,
@required Stream<T> value,
T initialData,
ErrorBuilder<T> catchError,
UpdateShouldNotify<T> updateShouldNotify,
bool lazy,
TransitionBuilder builder,
Widget child}
)
This constructor notifies the changed values to the StreamProvider children.

Ex: StreamProvider<Model>(create: (context) =>
Model(),)
  • ValueListenableProvider<T> This class receives changes in value by subscribing to a ValueListenable.
ValueListenableProvider<T>.value(
{Key key,
@required ValueListenable<T> value,
UpdateShouldNotify<T> updateShouldNotify,
TransitionBuilder builder,
Widget child}
)
This constructor shows the changed values to its children.

Apart from these, there are a number of other classes that are available depending upon the need but these are the most used. Let's understand the concept with an example.

Step-by-Step Implementation

Step 1: Create a new Flutter Application

Create a new Flutter application using the command Prompt. To create a new app, write the following command and run it.

flutter create app_name

To know more about it refer this article: Creating a Simple Application in Flutter

Step 2: Adding the Dependency

To add the dependency to the pubspec.yaml file, add provider as a dependency in the dependencies part of the pubspec.yaml file, as shown below:

Dart
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.4

Now, run the command below in the terminal.

flutter pub get

Or

Run the command below in the terminal.

flutter pub add provider

Step 3: Import dependencies

To use libraries, import all of them in the respective .dart file,

import 'package:provider/provider.dart';

Step 4: Folder Structure

Follow the file structure below for better understanding.

folder_structure

Step 5: Start Coding

First of all, we will be defining a model library inside of the lib folder which consists of item.dart and item_data.dart. Apart from these, the lib will have 3 more dart files namely the main.dart, home.dart, and item_list.dart. 

The item.dart is a simple class that defines what are the attributes that the item class will hold and a toggle method.

- item.dart:

Dart
import 'package:flutter/foundation.dart';

class Item {
  String item;
  bool completed;
  Item({@required this.item, this.completed = false});
  void toggle() {
    completed = !completed;
  }
}


The item_data.dart contains a list that will hold the data of the Item class defined above. There are methods to perform tasks such as add, toggle, and remove an item from the list.

- item_data.dart:

Dart
import 'dart:collection';
import 'package:flutter/foundation.dart';
import '../model/item.dart';

class ItemData with ChangeNotifier {
  List<Item> _items = [];
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  get size => _items.length;

  void addItem(Item item) {
    _items.add(item);
    notifyListeners();
  }

  void toggleItem(Item item) {
    item.toggle();
    notifyListeners();
  }

  void removeItem(Item item) {
    _items.remove(item);
    notifyListeners();
  }
}


The item_list.dart creates a ListView builder of the data coming from the list. It uses the Consumer to get the data.

- item_list.dart:

Dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'model/item_data.dart';

class ItemList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ItemData>(builder: (context, data, child) {
      return ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: data.size,
        itemBuilder: (context, index) {
          final item = data.items[index];
          return GestureDetector(
            onLongPress: () => data.removeItem(item),
            child: Container(
              padding: EdgeInsets.symmetric(vertical: 5),
              child: ListTile(
                leading: CircleAvatar(
                  backgroundColor: Colors.blueGrey,
                  child: Text(item.item[0]),
                ),
                title: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      item.item,
                      style: TextStyle(
                          decoration: item.completed
                              ? TextDecoration.lineThrough
                              : null,
                          fontSize: 16,
                          fontWeight: FontWeight.bold),
                    ),
                    Checkbox(
                      value: item.completed,
                      onChanged: (c) => data.toggleItem(item),
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      );
    });
  }
}


Step 7: Develop UI

- showDialog: Used to show an AlertDialog for adding a list member.

Dart
 showDialog(
    context: context,
    builder: (context) {
      String item = '';
      return AlertDialog(
        title: const Text('Add Item'),
        content: TextField(
          onChanged: (value) {
            item = value;
          },
          decoration: const InputDecoration(hintText: 'Enter item name'),
        ),
        actions: [
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.green,
              foregroundColor: Colors.white,
            ),
            onPressed: () {
              if (item.isNotEmpty) {
                Provider.of<ItemData>(context, listen: false).addItem(Item(item: item));
                Navigator.pop(context);
              }
            },
            child: const Text('Add'),
          ),
        ],
      );
    },
);


-main.dart:

Dart
import 'package:flutter/material.dart';
import 'package:flutter_geeks/item.dart';
import 'package:flutter_geeks/item_list.dart'; 
import 'package:provider/provider.dart'; 
import './item_data.dart'; 

// Entry point of the Flutter application
void main() { 
  runApp(MyApp()); 
} 

// Main application widget
class MyApp extends StatelessWidget { 
  @override 
  Widget build(BuildContext context) { 
    return ChangeNotifierProvider( 
      // Providing the ItemData model to the widget tree
      create: (context) => ItemData(), 
      child: MaterialApp( 
        debugShowCheckedModeBanner: false,
        title: 'Provider Demo', 
        home: Home(), // Sets the Home widget as the initial screen
      ), 
    ); 
  } 
}

// Home widget, which is a stateful widget
class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

// State class for the Home widget
class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider Demo'),
        backgroundColor: Colors.green, 
        foregroundColor: Colors.white, 
      ),
      body: ItemList(), // Displays the list of items
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.green, 
        foregroundColor: Colors.white, 
        onPressed: () {
          // Opens a dialog to add a new item
          showDialog(
            context: context,
            builder: (context) {
              String item = ''; // Variable to store the entered item name
              return AlertDialog(
                title: const Text('Add Item'), 
                content: TextField(
                  onChanged: (value) {
                    item = value; // Updates the item name as the user types
                  },
                  decoration: const InputDecoration(hintText: 'Enter item name'), // Hint text for the input field
                ),
                actions: [
                  ElevatedButton(
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.green, 
                      foregroundColor: Colors.white, 
                    ),
                    onPressed: () {
                      if (item.isNotEmpty) {
                        // Adds the item to the list if the input is not empty
                        Provider.of<ItemData>(context, listen: false).addItem(Item(item: item));
                        Navigator.pop(context); // Closes the dialog
                      }
                    },
                    child: const Text('Add'), 
                  ),
                ],
              );
            },
          );
        },
        child: const Icon(Icons.add), // Icon for the floating action button
      ),
    );
  }
}

The main.dart has a ChangeNotifierProvider which acts as a parent to the material app. As our app is quite small we have defined the provider at the top only. In case your app is quite large you can place the provider at the top of the widget that needs the data and not at the top.

Complete Source Code:

item.dart
class Item {
  String item;
  bool completed;
  Item({required this.item, this.completed = false});
  void toggle() {
    completed = !completed;
  }
}
item_data.dart
import 'dart:collection';
import 'package:flutter/foundation.dart';
import './item.dart';

class ItemData with ChangeNotifier {
  List<Item> _items = [];
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  get size => _items.length;

  void addItem(Item item) {
    _items.add(item);
    notifyListeners();
  }

  void toggleItem(Item item) {
    item.toggle();
    notifyListeners();
  }

  void removeItem(Item item) {
    _items.remove(item);
    notifyListeners();
  }
}
item_list.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './item_data.dart';

class ItemList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ItemData>(
      builder: (context, data, child) {
        return ListView.builder(
          scrollDirection: Axis.vertical,
          shrinkWrap: true,
          itemCount: data.size,
          itemBuilder: (context, index) {
            final item = data.items[index];
            return GestureDetector(
              onLongPress: () => data.removeItem(item),
              child: Container(
                padding: EdgeInsets.symmetric(vertical: 5),
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: Colors.green,
                    foregroundColor: Colors.white,
                    child: Text(item.item[0]),
                  ),
                  title: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(
                        item.item,
                        style: TextStyle(
                          decoration:
                              item.completed
                                  ? TextDecoration.lineThrough
                                  : null,
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Checkbox(
                        value: item.completed,
                        onChanged: (c) => data.toggleItem(item),
                      ),
                    ],
                  ),
                ),
              ),
            );
          },
        );
      },
    );
  }
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_geeks/item.dart';
import 'package:flutter_geeks/item_list.dart'; 
import 'package:provider/provider.dart'; 
import './item_data.dart'; 

// Entry point of the Flutter application
void main() { 
  runApp(MyApp()); 
} 

// Main application widget
class MyApp extends StatelessWidget { 
  @override 
  Widget build(BuildContext context) { 
    return ChangeNotifierProvider( 
      // Providing the ItemData model to the widget tree
      create: (context) => ItemData(), 
      child: MaterialApp( 
        debugShowCheckedModeBanner: false, // Disables the debug banner
        title: 'Provider Demo', 
        theme: ThemeData( 
          primarySwatch: Colors.green, // Sets the primary theme color
        ), 
        home: Home(), // Sets the Home widget as the initial screen
      ), 
    ); 
  } 
}

// Home widget, which is a stateful widget
class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

// State class for the Home widget
class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider Demo'),
        backgroundColor: Colors.green, 
        foregroundColor: Colors.white, 
      ),
      body: ItemList(), // Displays the list of items
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.green, 
        foregroundColor: Colors.white, 
        onPressed: () {
          // Opens a dialog to add a new item
          showDialog(
            context: context,
            builder: (context) {
              String item = ''; // Variable to store the entered item name
              return AlertDialog(
                title: const Text('Add Item'), 
                content: TextField(
                  onChanged: (value) {
                    item = value; // Updates the item name as the user types
                  },
                  decoration: const InputDecoration(hintText: 'Enter item name'), // Hint text for the input field
                ),
                actions: [
                  ElevatedButton(
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.green, 
                      foregroundColor: Colors.white, 
                    ),
                    onPressed: () {
                      if (item.isNotEmpty) {
                        // Adds the item to the list if the input is not empty
                        Provider.of<ItemData>(context, listen: false).addItem(Item(item: item));
                        Navigator.pop(context); // Closes the dialog
                      }
                    },
                    child: const Text('Add'), 
                  ),
                ],
              );
            },
          );
        },
        child: const Icon(Icons.add), // Icon for the floating action button
      ),
    );
  }
}


Output:

Apart from Provider, there are other State Management tools also available such as -

  • StatefulWidget: These are the widgets provided in the material package. These widgets have an internal state which can be re-built if the input changes or if the widget’s state changes.
  • InheritedWidget: These are simple widgets that hold the data that is to be used by their children or descendants. These provide a simple mechanism to move data to a child much below in the widget tree. There is an element associated with it that changes the data when the element updates. The provider package is a wrapper around the InheritedWidgets.
  • ScopedModel: This library is taken from the Fuchsia codebase which provides a method to pass the data from Parents to their children. When the model changes the children are rebuilt. Our class can extend the Model class to create our Models. ScopedModel Widget is wrapped around the widget whose data needs to be sent down the widget tree. ScopedModelDescendant Widget is used to listen to changes that happen to the Model and rebuild the child.
  • BLoC: Business Logic Component. This technique allows us to handle data as a Stream of events. It comes between the UI and the data that handles the logic for the application. The main components of BLoC are Sink and Stream. The StreamController handles these components. The sink is used to add data/events and the Stream is used to listen to them.

Next Article
Article Tags :

Similar Reads