会监听其他 Provider 的 Provider
本课属于 Coddy Flutter 学习旅程的“状态管理”单元。第 20 课,共 27 课。
有些状态可以从其他状态推导出来:总分钟数就是各项锻炼的分钟数之和。你可以在每个显示总数的组件中把它们加起来,也可以只写一次,把它写成一个监听另一个提供器的提供器:
final totalMinutesProvider = Provider((ref) {
final workouts = ref.watch(workoutsProvider);
return workouts.fold(0, (sum, w) => sum + w.minutes);
});提供器的函数也会收到一个 ref。在函数中使用 ref.watch,Riverpod 就会记住总分钟数依赖于各项锻炼。添加一项锻炼后,总分钟数会重新计算,所有监听总分钟数的组件也会重新构建。
这里要使用 ref.watch,不要使用 ref.read。read 只会在第一次计算总分钟数时读取一次列表,此后总分钟数就不会再变化。
这就是之前提到的 getter 思路,只不过应用到了整个应用:尽可能少存储数据,其余数据都根据它推导出来。
挑战
中等顶部栏会监听 totalMinutesProvider,但该提供者始终返回 0。
- 让它监听
workoutsProvider,并返回所有锻炼的minutes总和。
亲手试试
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')),
],
),
),
],
),
);
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
状态管理中的所有课
4Riverpod
Riverpod:ProviderScopeConsumerWidget 与 ref.watchRiverpod:StateProviderRiverpod:Notifier会监听其他 Provider 的 Provider选择状态的存放位置自己练习: Flutter 在线编译器