Menu
CoddyTech

Провайдеры, отслеживающие другие провайдеры

Часть раздела Управление состоянием путешествия по Flutter на Coddy. Урок 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 получает список только один раз, когда общее количество вычисляется впервые, и после этого оно никогда не меняется.

Это идея геттера, о которой говорилось ранее, применённая ко всему приложению: храните как можно меньше данных, а остальное вычисляйте на их основе.

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