Menu
CoddyTech

会监听其他 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 思路,只不过应用到了整个应用:尽可能少存储数据,其余数据都根据它推导出来。

challenge icon

挑战

中等

顶部栏会监听 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')),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

状态管理中的所有课

自己练习: Flutter 在线编译器