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
MediumThe top bar watches totalMinutesProvider, but that provider always returns 0.
- Make it watch
workoutsProviderand return the sum of every workout'sminutes.
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')),
],
),
),
],
),
);
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in State Management
1Lifting State Up
Pass State to a ChildPass a Callback to a ChildCallbacks That Send a ValueLift State Up4Riverpod
Riverpod: ProviderScopeConsumerWidget and ref.watchRiverpod: StateProviderRiverpod: NotifierProviders That Watch ProvidersChoosing Where State LivesPractice on your own: Online Flutter compiler