使用Riverpod在Flutter中创建Todo列表

使用Riverpod在Flutter中创建Todo列表

视频

www.bilibili.com/video/BV1jj...

前言

原文 ducafecat.com/blog/flutte...

学习如何使用Riverpod在Flutter中构建一个功能完整的Todo列表应用。通过Consumer组件、ConsumerStatefulWidget类、ref.read方法和provider build重写,了解Riverpod的状态管理和更新状态机制。

参考

pub.dev/packages/ri...

riverpod.dev/

flutter.ducafecat.com/

知识点

  1. Consumer 组件: Consumer组件是Riverpod提供的用于订阅和监听Provider数据变化的组件。它接收一个或多个Provider,并在数据发生变化时重新构建其子组件。
  2. ConsumerStatefulWidget 类: ConsumerStatefulWidget是一个抽象类,继承自StatefulWidget。通过继承ConsumerStatefulWidget类并实现其createState方法,可以创建一个具有可变状态和自动重建机制的组件。
  3. ref.read 方法: ref.read方法用于从ProviderContainer中获取Provider的值,而无需订阅或监听它。它适用于在不需要实时更新的情况下获取Provider的当前值。
  4. provider build 重写: 通过重写Provider的build方法,可以在Provider的值发生变化时重新构建其依赖项。这使得我们可以控制在数据变化时如何更新UI。
  5. update state 更新状态: 在Riverpod中,状态更新是通过修改可变的Provider值或使用 invalidateSelf 来完成,触发UI的重建。

步骤

实例对象 TodoEntity

lib/entity/todo.dart

dart 复制代码
class TodoEntity {
  final int? id;
  final String? title;
  final String? description;
  final bool? completed;

  TodoEntity({this.id, this.title, this.description, this.completed});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'completed': completed,
    };
  }

  static TodoEntity fromMap(Map<String, dynamic> map) {
    return TodoEntity(
      id: map['id'],
      title: map['title'],
      description: map['description'],
      completed: map['completed'],
    );
  }
}

实现 todo provider

lib/provider/todo_list.dart

定义 TodoList provider 类,todo 数据集合

dart 复制代码
part 'todo_list.g.dart';

@riverpod
class TodoList extends _$TodoList {
  static List<TodoEntity> items = [];

  @override
  Future<List<TodoEntity>> build() async {
    return items;
  }
  
  ...

实现新增 add 功能

dart 复制代码
  Future<void> addTodo(TodoEntity todo) async {
    // await http.post(
    //   Uri.https('your_api.com', '/todos'),
    //   // 我们序列化 Todo 对象并将其 POST 到服务器。
    //   headers: {'Content-Type': 'application/json'},
    //   body: jsonEncode(todo.toJson()),
    // );

    // 延迟 1 秒
    await Future.delayed(const Duration(seconds: 1));

    // 我们将新的 Todo 添加到 items 列表中。
    items.add(todo);
  }

Consumer 新增 todo 按钮栏

lib/pages/todo/widgets/bar.dart

TodoBarWidget 类

dart 复制代码
class TodoBarWidget extends StatelessWidget {
  const TodoBarWidget({super.key});

Consumer 方式实现关联 riverpod

dart 复制代码
  Widget _buildView() {
    // Consumer 提供监听功能的小部件
    return Consumer(builder: (
      BuildContext context,
      WidgetRef ref,
      Widget? widget,
    ) {
      return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: () {
              // 事件处理用 read 方式读取 provider
              ref.read(todoListProvider.notifier).addTodo(
                    TodoEntity(
                      description: 'This is a new todo',
                    ),
                  );
            },
            child: const Text('Add todo'),
          ),
        ],
      );
    });
  }

注意使用 ref.read,虽然 ref.watch 也能用

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return _buildView();
  }

状态更新两种方式

lib/provider/todo_list.dart

直接设置新状态值

dart 复制代码
  Future<void> addTodo(TodoEntity todo) async {
    ...
    
    // 直接设置 state 值
    state = AsyncData(items);
  }

将本地缓存标记为脏

dart 复制代码
  Future<void> addTodo(TodoEntity todo) async {
    ...
     
    // 将本地缓存标记为脏
    ref.invalidateSelf();
    // 重新构建 TodoList, 呼叫 build 方法
    await future;
  }

ConsumerWidget 监听 todo 列表

lib/pages/todo/widgets/list.dart

TodoListWidget 类 继承 ConsumerWidget

dart 复制代码
class TodoListWidget extends ConsumerWidget {
  const TodoListWidget({super.key});

监听 todo 列表

dart 复制代码
  Widget _buildList(ref) {
    var todos = ref.watch(todoListProvider);
    return switch (todos) {
      AsyncData<List<TodoEntity>>(:final value) => ListView.builder(
          itemCount: value.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(value[index].description ?? ""),
              subtitle: Text((value[index].completed ?? false) ? "yes" : "no"),
            );
          },
        ),
      AsyncError() => const Text('Oops, something unexpected happened'),
      _ => const CircularProgressIndicator(),
    };
  }
dart 复制代码
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return _buildList(ref);
  }

build 回调会有一个 ref 对象,直接传入 _buildList 使用

todo 主界面实现

lib/pages/todo/index.dart

TodoPage 类,使用 StatelessWidget 就行

dart 复制代码
class TodoPage extends StatelessWidget {
  const TodoPage({super.key});

界面 build

dart 复制代码
  Widget _buildView(BuildContext context) {
    return const Center(
      child: Column(
        children: <Widget>[
          // StatelessWidget 的组件
          TodoBarWidget(),

          // StatefulWidget 的组件
          // TodoBarStfWidget(),

          // ConsumerWidget 的列表
          Expanded(
            child: TodoListWidget(),
          ),
        ],
      ),
    );
  }
dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todo List')),
      body: _buildView(context),
    );
  }

加入启动菜单

lib/pages/index.dart

dart 复制代码
  Widget _buildView(BuildContext context) {
    return Center(
      ...
      
          _buildBtn(context, '03 TODO 列表', const TodoPage()),
        ],
      ),
    );
  }

ConsumerStatefulWidget 界面 UI 交互

编写 TodoBarStfWidget 继承 ConsumerStatefulWidget

可以用插件 Flutter Riverpod Snippets 简化输入代码块

marketplace.visualstudio.com/items?itemN...

dart 复制代码
class TodoBarStfWidget extends ConsumerStatefulWidget {
  const TodoBarStfWidget({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() =>
      _TodoBarStfWidgetState();
}

class _TodoBarStfWidgetState extends ConsumerState<TodoBarStfWidget> {

定义 Future 状态对象监听变化

dart 复制代码
  // 待处理的 addTodo 操作。如果没有待处理的,则为 null。
  Future<void>? _pendingAddTodo;

通过 FutureBuilder 更新 UI

dart 复制代码
  Widget _buildView() {
    return FutureBuilder(
      // 我们监听待处理的操作,以相应地更新 UI。
      future: _pendingAddTodo,
      builder: (context, snapshot) {
        // 计算是否存在错误状态。
        // 检查 connectionState 用于在重试操作时进行处理。
        // 是否错误
        final isErrored = snapshot.hasError &&
            snapshot.connectionState != ConnectionState.waiting;
        // 是否等待
        final isWaiting = snapshot.connectionState == ConnectionState.waiting;

        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 按钮
            ElevatedButton(
              style: ButtonStyle(
                // 如果出现错误,我们会将该按钮显示为红色
                backgroundColor: MaterialStateProperty.all(
                  isErrored ? Colors.red : null,
                ),
              ),
              // 设置 null 后按钮灰色禁止点击
              onPressed: isWaiting == true
                  ? null
                  : () {
                      // 我们将 addTodo 返回的 future 保存在变量中
                      final future = ref
                          .read(todoListProvider.notifier)
                          .addTodo(
                              TodoEntity(description: 'This is a new todo'));

                      // 我们将这个 future 存储在本地的状态中
                      setState(() {
                        _pendingAddTodo = future;
                      });
                    },
              child: const Text('Add todo V2'),
            ),

            // 操作正在等待,让我们显示一个进度指示器
            if (isWaiting) ...[
              const SizedBox(width: 8),
              const CircularProgressIndicator(),
            ]
          ],
        );
      },
    );
  }
dart 复制代码
  @override
  Widget build(BuildContext context) {
    return _buildView();
  }

代码

github.com/ducafecat/f...

小结

本文介绍了如何使用Riverpod在Flutter中实现一个Todo列表功能。通过Consumer组件和ConsumerStatefulWidget类,我们可以订阅和监听数据变化,并在需要时更新UI。使用ref.read方法可以获取Provider的值,从而实现数据的读取和操作。通过provider build重写,我们可以在数据变化时重新构建UI。这篇文章帮助读者更深入地了解了如何使用Riverpod进行状态管理,并实现了一个基本的Todo列表应用。

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


© 猫哥 ducafecat.com

end

相关推荐
江上清风山间明月15 小时前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能1 天前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人1 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen1 天前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang1 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang1 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1231 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-2 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode
freflying11192 天前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins
机器瓦力2 天前
Flutter应用开发:对象存储管理图片
flutter