Menu
CoddyTech

Providers That Watch Providers

Part of the State Management section of Coddy's Flutter journey. Lesson 20 of 27.

Some state follows from other state: the total minutes are the sum of the workouts. You could add them up in every widget that shows a total, or write it once, as a provider that watches another provider:

final totalMinutesProvider = Provider((ref) {
  final workouts = ref.watch(workoutsProvider);
  return workouts.fold(0, (sum, w) => sum + w.minutes);
});

A provider's function gets a ref too. With ref.watch inside it, Riverpod remembers that the total depends on the workouts. Add a workout, and the total is worked out again, and every widget watching the total builds again.

Use ref.watch here, not ref.read. read takes the list once, when the total is first worked out, and the total never changes after that.

This is the getter idea from earlier, for the whole app: store as little as you can, and work the rest out from it.

challenge icon

Challenge

Medium

The top bar watches totalMinutesProvider, but that provider always returns 0.

  • Make it watch workoutsProvider and return the sum of every workout's minutes.

Try it yourself

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class Workout {
  Workout(this.name, this.minutes);

  final String name;
  final int minutes;
}

class WorkoutLog extends Notifier<List<Workout>> {
  @override
  List<Workout> build() => [];

  void add(Workout workout) {
    state = [...state, workout];
  }
}

final workoutsProvider = NotifierProvider<WorkoutLog, List<Workout>>(WorkoutLog.new);

final totalMinutesProvider = Provider((ref) {
  return 0;
});

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: LogPage(),
    );
  }
}

class LogPage extends ConsumerWidget {
  const LogPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final workouts = ref.watch(workoutsProvider);
    final total = ref.watch(totalMinutesProvider);
    return Scaffold(
      appBar: AppBar(title: Text('Total: $total min')),
      body: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () => ref.read(workoutsProvider.notifier).add(Workout('Run', 30)),
                child: const Text('Run'),
              ),
              const SizedBox(width: 16),
              ElevatedButton(
                onPressed: () => ref.read(workoutsProvider.notifier).add(Workout('Yoga', 20)),
                child: const Text('Yoga'),
              ),
            ],
          ),
          Expanded(
            child: ListView(
              children: [
                for (final w in workouts) ListTile(title: Text(w.name), trailing: Text('${w.minutes} min')),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in State Management

Practice on your own: Online Flutter compiler