Menu
CoddyTech

Riverpod:Notifier

本课属于 Coddy Flutter 学习旅程的“状态管理”单元。第 19 课,共 27 课。

StateProvider 适合存储一个数字。如果状态还带有规则,比如列表需要提供添加和删除方法,就应该编写一个 Notifier:这是一个保存状态及其修改方法的类。

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

  void add(String name) {
    state = [...state, name];
  }
}

final guestsProvider =
    NotifierProvider<GuestList, List<String>>(GuestList.new);

build 会返回初始状态,也就是一个空列表。在类中,state 表示当前值。GuestList.new 是这个类的构造函数,会传给 provider,让它可以创建这个 notifier。

关键规则:给 state 赋一个新值,绝不要修改旧值。只有在 state 被设为另一个对象时,Riverpod 才会通知各个 widget。state.add(name) 会直接修改原列表,所以 state 仍然是同一个列表,不会触发通知。[...state, name] 会创建一个新列表:三个点会把所有旧名字展开到新列表中,然后把新名字放在末尾。

widget 可以通过 notifier 调用方法:ref.read(guestsProvider.notifier).add('Ada')。这个页面使用 ConsumerStatefulWidget,也就是有状态的类型,因为它还需要保存一个文本控制器。它的 State 将 ref 作为一个字段。

challenge icon

挑战

困难

访客列表会显示 guestsProvider 中的姓名,但 Add 只会清空输入框。

  • 为 GuestList 添加一个方法 add(String name),将 state 设置为一个新列表,并把姓名放在末尾。
  • 让 Add 在清空输入框之前,使用输入框中的文本调用它。

亲手试试

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

class GuestList extends Notifier<List<String>> {
  @override
  List<String> build() => [];
}

final guestsProvider = NotifierProvider<GuestList, List<String>>(GuestList.new);

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: PartyPage(),
    );
  }
}

class PartyPage extends ConsumerStatefulWidget {
  const PartyPage({super.key});

  @override
  ConsumerState<PartyPage> createState() => _PartyPageState();
}

class _PartyPageState extends ConsumerState<PartyPage> {
  final controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final guests = ref.watch(guestsProvider);
    return Scaffold(
      appBar: AppBar(title: Text('Guests: ${guests.length}')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: controller,
                    decoration: const InputDecoration(labelText: 'Guest name'),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: () {
                    controller.clear();
                  },
                  child: const Text('Add'),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView(
              children: [
                for (final name in guests) ListTile(leading: const Icon(Icons.person), title: Text(name)),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
quiz icon自我测试

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

状态管理中的所有课

自己练习: Flutter 在线编译器