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: add a parent ProviderScope in our main() function:

Future<void> main() async {
    child: MyApp(),

Step 2: create a Provider<HiveDataStore>:

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

Step 3: use it in the HomePage, which is now a ConsumerWidget:

// 1. extend from ConsumerWidget
class HomePage extends ConsumerWidget {
  // 2. Add an additional WidgetRef argument
  Widget build(BuildContext context, WidgetRef 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 WidgetRef

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 WidgetRef 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 WidgetRef to access any provider by reference.

Watching a provider

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

This is useful when we use providers such as StateNotifierProviderFutureProvider or StreamProvider. Read my Riverpod Ultimate Guide for more info.


Complete and Continue