使用Riverpod在Flutter中创建Todo列表
视频
https://www.bilibili.com/video/BV1jj42197c8/
前言
原文 https://ducafecat.com/blog/flutter-todo-list-with-riverpod-guide-02
学习如何使用Riverpod在Flutter中构建一个功能完整的Todo列表应用。通过Consumer组件、ConsumerStatefulWidget类、ref.read方法和provider build重写,了解Riverpod的状态管理和更新状态机制。
参考
https://pub.dev/packages/riverpod
https://flutter.ducafecat.com/
知识点
- Consumer 组件:
Consumer组件是Riverpod提供的用于订阅和监听Provider数据变化的组件。它接收一个或多个Provider,并在数据发生变化时重新构建其子组件。 - ConsumerStatefulWidget 类:
ConsumerStatefulWidget是一个抽象类,继承自StatefulWidget。通过继承ConsumerStatefulWidget类并实现其createState方法,可以创建一个具有可变状态和自动重建机制的组件。 - ref.read 方法:
ref.read方法用于从ProviderContainer中获取Provider的值,而无需订阅或监听它。它适用于在不需要实时更新的情况下获取Provider的当前值。 - provider build 重写:
通过重写Provider的build方法,可以在Provider的值发生变化时重新构建其依赖项。这使得我们可以控制在数据变化时如何更新UI。 - update state 更新状态:
在Riverpod中,状态更新是通过修改可变的Provider值或使用 invalidateSelf 来完成,触发UI的重建。
步骤
实例对象 TodoEntity
lib/entity/todo.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 数据集合
part 'todo_list.g.dart';
class TodoList extends _$TodoList {
static List<TodoEntity> items = [];
Future<List<TodoEntity>> build() async {
return items;
}
...
实现新增 add 功能
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 类
class TodoBarWidget extends StatelessWidget {
const TodoBarWidget({
super.key});
Consumer 方式实现关联 riverpod
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 也能用
build(BuildContext context) {
return _buildView();
}
Widget
状态更新两种方式
lib/provider/todo_list.dart
直接设置新状态值
Future<void> addTodo(TodoEntity todo) async {
...
// 直接设置 state 值
state = AsyncData(items);
}
将本地缓存标记为脏
Future<void> addTodo(TodoEntity todo) async {
...
// 将本地缓存标记为脏
ref.invalidateSelf();
// 重新构建 TodoList, 呼叫 build 方法
await future;
}
ConsumerWidget 监听 todo 列表
lib/pages/todo/widgets/list.dart
TodoListWidget 类 继承 ConsumerWidget
class TodoListWidget extends ConsumerWidget {
const TodoListWidget({
super.key});
监听 todo 列表
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(),
};
}
build(BuildContext context, WidgetRef ref) {
return _buildList(ref);
}
Widget
build 回调会有一个 ref 对象,直接传入 _buildList 使用
todo 主界面实现
lib/pages/todo/index.dart
TodoPage 类,使用 StatelessWidget 就行
class TodoPage extends StatelessWidget {
const TodoPage({
super.key});
界面 build
Widget _buildView(BuildContext context) {
return const Center(
child: Column(
children: <Widget>[
// StatelessWidget 的组件
TodoBarWidget(),
// StatefulWidget 的组件
// TodoBarStfWidget(),
// ConsumerWidget 的列表
Expanded(
child: TodoListWidget(),
),
],
),
);
}
build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Todo List')),
body: _buildView(context),
);
}
Widget
加入启动菜单
lib/pages/index.dart
Widget _buildView(BuildContext context) {
return Center(
...
_buildBtn(context, '03 TODO 列表', const TodoPage()),
],
),
);
}
ConsumerStatefulWidget 界面 UI 交互
编写 TodoBarStfWidget 继承 ConsumerStatefulWidget
可以用插件 Flutter Riverpod Snippets 简化输入代码块
https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets
class TodoBarStfWidget extends ConsumerStatefulWidget {
const TodoBarStfWidget({
super.key});
ConsumerState<ConsumerStatefulWidget> createState() =>
_TodoBarStfWidgetState();
}
class _TodoBarStfWidgetState extends ConsumerState<TodoBarStfWidget> {
定义 Future 状态对象监听变化
// 待处理的 addTodo 操作。如果没有待处理的,则为 null。
Future<void>? _pendingAddTodo;
通过 FutureBuilder 更新 UI
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(),
]
],
);
},
);
}
build(BuildContext context) {
return _buildView();
}
Widget
代码
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_riverpod
小结
本文介绍了如何使用Riverpod在Flutter中实现一个Todo列表功能。通过Consumer组件和ConsumerStatefulWidget类,我们可以订阅和监听数据变化,并在需要时更新UI。使用ref.read方法可以获取Provider的值,从而实现数据的读取和操作。通过provider build重写,我们可以在数据变化时重新构建UI。这篇文章帮助读者更深入地了解了如何使用Riverpod进行状态管理,并实现了一个基本的Todo列表应用。
感谢阅读本文
如果有什么建议,请在评论中让我知道。我很乐意改进。
© 猫哥
ducafecat.com
end