基于Riverpod实现待办App一对多关系的异步初始化问题
基于Riverpod实现待办App的Groups与Tasks一对多关系
我要实现一个简易待办App,包含Groups和Tasks两个实体,一个Group对应多个Task。已实现两个Riverpod Provider,使用sqflite操作SQLite数据库。但因数据库读取是异步任务,在_TasksInCardState中首次调用时tasks为空,需解决基于Riverpod的一对多关系实现问题。
现有Provider代码
groups_provider.dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:todo/models/group.dart'; import 'package:todo/db/db_helper.dart' as dbHelper; import 'package:todo/providers/tasks_provider.dart'; class GroupsNotifier extends StateNotifier<List<Group>> { GroupsNotifier() : super([]); void loadGroups() async { state = await dbHelper.getAllGroups(); } void addGroup(String title, Color color) async { final newId = await dbHelper.addGroup(title, color); final newGroup = Group(id: newId, title: title, color: color); state = [...state, newGroup]; } } final groupsProvider = StateNotifierProvider<GroupsNotifier, List<Group>>((ref) { return GroupsNotifier(); });
tasks_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:todo/models/group.dart'; import 'package:todo/models/task.dart'; import 'package:todo/db/db_helper.dart' as dbHelper; import 'package:todo/providers/groups_provider.dart'; class TasksNotifier extends StateNotifier<Map<Group, List<Task>>> { TasksNotifier() : super({}); void loadTasks(Group group) async { state = {...state, group: await dbHelper.getTasksByGroup(group)}; } void addTask(String title, Group group, bool done) async { final newId = await dbHelper.addTask(title, group, done); final newTask = Task(id: newId, title: title, group: group, done: done); state = { ...state, group: [...state[group] ?? [], newTask] }; } void toggleDone(Task task) { final group = task.group; final List<Task> tasks = state[group]!; final index = tasks.indexOf(task); List<Task> newList = []; bool newValue = false; for (var i = 0; i < state.length; i++) { newValue = index == i ? !tasks[i].done : tasks[i].done; newList.add( Task( id: tasks[i].id, title: tasks[i].title, group: tasks[i].group, done: newValue), ); } dbHelper.changeTaskDone(task, newValue); state = {...state, group: newList}; } } final tasksProvider = StateNotifierProvider<TasksNotifier, Map<Group, List<Task>>>((ref) { return TasksNotifier(); });
现有UI代码片段
class _TasksInCardState extends ConsumerState<TasksInCard> { List<Task> tasks = []; @override void initState() { super.initState(); ref.read(tasksProvider.notifier).loadTasks(widget.group); } @override Widget build(BuildContext context) { tasks = ref.watch(tasksProvider)[widget.group]??[]; return Expanded( child: Directionality( textDirection: TextDirection.rtl, child: ListView.builder( // ... 任务列表构建逻辑 ) ) ); } }
问题分析
当前实现中,tasksProvider初始状态为空Map,loadTasks是异步操作,首次Build时ref.watch(tasksProvider)[widget.group]返回null,导致tasks为空数组,UI会先显示空内容,直到异步加载完成后状态更新才会重新Build显示任务。同时,Group作为Map的key,若未正确实现==和hashCode,会导致无法识别同一Group对象,重复加载任务。
解决方案
1. 优化TasksNotifier,维护每个Group的异步状态
将TasksNotifier的状态改为Map<Group, AsyncValue<List<Task>>>,为每个Group单独维护加载中、成功、失败状态:
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:todo/models/group.dart'; import 'package:todo/models/task.dart'; import 'package:todo/db/db_helper.dart' as dbHelper; class TasksNotifier extends StateNotifier<Map<Group, AsyncValue<List<Task>>>> { TasksNotifier() : super({}); void loadTasks(Group group) async { // 避免重复加载 if (state.containsKey(group)) return; // 标记为加载中 state = {...state, group: const AsyncLoading()}; try { final tasks = await dbHelper.getTasksByGroup(group); state = {...state, group: AsyncData(tasks)}; } catch (e) { state = {...state, group: AsyncError(e, StackTrace.current)}; } } void addTask(String title, Group group, bool done) async { final newId = await dbHelper.addTask(title, group, done); final newTask = Task(id: newId, title: title, group: group, done: done); final currentTasks = state[group]?.value ?? []; state = { ...state, group: AsyncData([...currentTasks, newTask]) }; } void toggleDone(Task task) async { final group = task.group; final currentTasks = state[group]?.value ?? []; final index = currentTasks.indexOf(task); if (index == -1) return; final updatedTasks = List<Task>.from(currentTasks); final updatedTask = updatedTasks[index].copyWith(done: !updatedTasks[index].done); updatedTasks[index] = updatedTask; try { await dbHelper.changeTaskDone(task, updatedTask.done); state = {...state, group: AsyncData(updatedTasks)}; } catch (e) { state = {...state, group: AsyncError(e, StackTrace.current)}; } } } final tasksProvider = StateNotifierProvider<TasksNotifier, Map<Group, AsyncValue<List<Task>>>>((ref) { return TasksNotifier(); });
2. 完善Group类的相等性判断
确保Group作为Map的key能被正确识别,需实现==和hashCode:
class Group { final int id; final String title; final Color color; Group({required this.id, required this.title, required this.color}); @override bool operator ==(Object other) => identical(this, other) || other is Group && runtimeType == other.runtimeType && id == other.id && title == other.title && color.value == other.color.value; @override int get hashCode => id.hashCode ^ title.hashCode ^ color.value.hashCode; }
3. 修改UI层,处理异步状态
在_TasksInCardState中使用AsyncValue.when方法处理不同状态的UI展示:
class _TasksInCardState extends ConsumerState<TasksInCard> { @override void initState() { super.initState(); ref.read(tasksProvider.notifier).loadTasks(widget.group); } @override Widget build(BuildContext context) { final taskAsync = ref.watch(tasksProvider)[widget.group] ?? const AsyncLoading(); return taskAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, st) => Center(child: Text('加载失败: $e')), data: (tasks) { return Expanded( child: Directionality( textDirection: TextDirection.rtl, child: tasks.isEmpty ? const Center(child: Text('暂无任务')) : ListView.builder( itemCount: tasks.length, itemBuilder: (context, index) { final task = tasks[index]; return ListTile( title: Text(task.title), leading: Checkbox( value: task.done, onChanged: (_) => ref.read(tasksProvider.notifier).toggleDone(task), ), ); }, ), ), ); }, ); } }
4. 优化GroupsProvider初始化
让GroupsProvider自动加载分组,减少UI层手动调用:
class GroupsNotifier extends StateNotifier<List<Group>> { GroupsNotifier() : super([]) { // 初始化时自动加载分组 loadGroups(); } void loadGroups() async { state = await dbHelper.getAllGroups(); } // ... 其他方法不变 }
内容的提问来源于stack exchange,提问作者Mojtaba Mahamed
相关产品推荐
相关产品推荐

