使用Riverpod在Flutter中創(chuàng)建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的狀態(tài)管理和更新狀態(tài)機制。
參考
https://pub.dev/packages/riverpod
https://flutter.ducafecat.com/
知識點
- Consumer 組件:
Consumer組件是Riverpod提供的用于訂閱和監(jiān)聽Provider數據變化的組件精续。它接收一個或多個Provider坝锰,并在數據發(fā)生變化時重新構建其子組件。 - ConsumerStatefulWidget 類:
ConsumerStatefulWidget是一個抽象類重付,繼承自StatefulWidget顷级。通過繼承ConsumerStatefulWidget類并實現其createState方法,可以創(chuàng)建一個具有可變狀態(tài)和自動重建機制的組件确垫。 - ref.read 方法:
ref.read方法用于從ProviderContainer中獲取Provider的值弓颈,而無需訂閱或監(jiān)聽它。它適用于在不需要實時更新的情況下獲取Provider的當前值删掀。 - provider build 重寫:
通過重寫Provider的build方法翔冀,可以在Provider的值發(fā)生變化時重新構建其依賴項。這使得我們可以控制在數據變化時如何更新UI披泪。 - update state 更新狀態(tài):
在Riverpod中纤子,狀態(tài)更新是通過修改可變的Provider值或使用 invalidateSelf 來完成,觸發(fā)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';
@riverpod
class TodoList extends _$TodoList {
static List<TodoEntity> items = [];
@override
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 提供監(jiān)聽功能的小部件
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 也能用
@override
Widget build(BuildContext context) {
return _buildView();
}
狀態(tài)更新兩種方式
lib/provider/todo_list.dart
直接設置新狀態(tài)值
Future<void> addTodo(TodoEntity todo) async {
...
// 直接設置 state 值
state = AsyncData(items);
}
將本地緩存標記為臟
Future<void> addTodo(TodoEntity todo) async {
...
// 將本地緩存標記為臟
ref.invalidateSelf();
// 重新構建 TodoList, 呼叫 build 方法
await future;
}
ConsumerWidget 監(jiān)聽 todo 列表
lib/pages/todo/widgets/list.dart
TodoListWidget 類 繼承 ConsumerWidget
class TodoListWidget extends ConsumerWidget {
const TodoListWidget({super.key});
監(jiān)聽 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(),
};
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return _buildList(ref);
}
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(),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Todo List')),
body: _buildView(context),
);
}
加入啟動菜單
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});
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_TodoBarStfWidgetState();
}
class _TodoBarStfWidgetState extends ConsumerState<TodoBarStfWidget> {
定義 Future 狀態(tài)對象監(jiān)聽變化
// 待處理的 addTodo 操作说订。如果沒有待處理的,則為 null潮瓶。
Future<void>? _pendingAddTodo;
通過 FutureBuilder 更新 UI
Widget _buildView() {
return FutureBuilder(
// 我們監(jiān)聽待處理的操作陶冷,以相應地更新 UI。
future: _pendingAddTodo,
builder: (context, snapshot) {
// 計算是否存在錯誤狀態(tài)毯辅。
// 檢查 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 存儲在本地的狀態(tài)中
setState(() {
_pendingAddTodo = future;
});
},
child: const Text('Add todo V2'),
),
// 操作正在等待思恐,讓我們顯示一個進度指示器
if (isWaiting) ...[
const SizedBox(width: 8),
const CircularProgressIndicator(),
]
],
);
},
);
}
@override
Widget build(BuildContext context) {
return _buildView();
}
代碼
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_riverpod
小結
本文介紹了如何使用Riverpod在Flutter中實現一個Todo列表功能沾谜。通過Consumer組件和ConsumerStatefulWidget類膊毁,我們可以訂閱和監(jiān)聽數據變化,并在需要時更新UI基跑。使用ref.read方法可以獲取Provider的值婚温,從而實現數據的讀取和操作。通過provider build重寫媳否,我們可以在數據變化時重新構建UI栅螟。這篇文章幫助讀者更深入地了解了如何使用Riverpod進行狀態(tài)管理,并實現了一個基本的Todo列表應用篱竭。
感謝閱讀本文
如果有什么建議力图,請在評論中讓我知道。我很樂意改進室抽。
? 貓哥
ducafecat.com
end