How to use Provider, Consumer, and ConsumerWidget


Up until now we've been creating two instances of the HiveDataStore: one in the main.dart file and one inside the HomePage.

HiveDataStore is a dependency for the HomePage, and we should only create it once. We can do this by using Riverpod as a dependency injection solution.

Step 1 is to add a parent ProviderScope in our main() function:

// inside main()
  child: MyApp(),

Step 2 is to create a Provider<HiveDataStore>:

// hive_data_store.dart
final dataStoreProvider = Provider<HiveDataStore>((ref) {
  return HiveDataStore();

Step 3 is to use it in the HomePage:

// 1. extend from ConsumerWidget
class HomePage extends ConsumerWidget {
  // 2. Add an additional WidgetReference argument
  Widget build(BuildContext context, WidgetReference ref) {
    // 3. get the dataStore with
    final dataStore =;
    return ValueListenableBuilder(
      valueListenable: dataStore.tasksListenable(),
      builder: (_, Box<Task> box, __) => TasksGridPage(
        tasks: box.values.toList(),

As an alternative to ConsumerWidget, we can return a Consumer inside the build() method, but this results in more boilerplate code.

BuildContext vs WidgetReference

There are some similarities between the two:

  • with Flutter we can use the BuildContext to access ancestor widgets such as Navigator and MediaQuery.
  • with Riverpod we can use this WidgetReference to access all the providers that we have created.

And some differences:

  • BuildContext works inside the widget tree and we can only use it to access ancestor widgets.
  • Riverpod providers are defined in the global scope and live outside the widget tree. We can use WidgetReference to access any provider by reference.


Also note that calling inside a widget will cause it to rebuild every time the Provider's value changes.

This is useful when we providers such as StateNotifierProviderFutureProvider or StreamProvider. See my Essential Guide for more info.