如何基于Riverpod高效实现任务列表的自动刷新/重建功能?
问题描述
我开发了一个包含Task列表的简易应用,每个Task都有时间范围约束,定义如下:
@freezed class Task with _$Task { const factory Task({ @Default('') String title, required DateTime start, required DateTime end, }) = _Task; }
我通过如下StateNotifier提供者展示任务:
class TasksController extends StateNotifier<List<Task>> { TasksController() : super(const []) { init(); } Future<void> init() async { state = await GetTasksFromSomeRepo(); } }
我希望显示任务正在进行的状态标识,目前采用Timer每秒调用setState全量重建Task列表组件的方案,在build方法中判断DateTime.now()是否处于Task的起止时间范围内,但该方案效率较低。
问题:
- 如何借助Riverpod实现更高效的自动刷新/重建功能?
解决方案
可以通过以下几种Riverpod优化方案实现高效刷新,避免全量重建:
方案一:使用独立的实时时间StreamProvider
创建一个每秒推送当前时间的StreamProvider,让单个Task组件同时监听自身Task数据和这个时间流,只有当时间变化且影响该Task的进行状态时,才重建单个组件:
// 定义实时时间的StreamProvider final currentTimeProvider = StreamProvider<DateTime>((ref) { return Stream.periodic(const Duration(seconds: 1), (_) => DateTime.now()); }); // 定义TasksController的Provider final tasksControllerProvider = StateNotifierProvider<TasksController, List<Task>>((ref) { return TasksController(); }); // 单个Task组件,仅监听自身Task和时间变化 class TaskItem extends ConsumerWidget { final Task task; const TaskItem({super.key, required this.task}); @override Widget build(BuildContext context, WidgetRef ref) { final currentTime = ref.watch(currentTimeProvider).value ?? DateTime.now(); final isActive = task.start.isBefore(currentTime) && task.end.isAfter(currentTime); return ListTile( title: Text(task.title), trailing: isActive ? const Icon(Icons.radio_button_checked) : const Icon(Icons.radio_button_unchecked), ); } } // 任务列表组件,仅监听任务列表变化,不监听时间 class TaskList extends ConsumerWidget { const TaskList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final tasks = ref.watch(tasksControllerProvider); return ListView.builder( itemCount: tasks.length, itemBuilder: (context, index) => TaskItem(task: tasks[index]), ); } }
这样处理的好处是:
- 任务列表组件仅在任务数据增删改时重建
- 单个Task组件仅在时间变化且自身活跃状态改变时重建,避免全量列表刷新
方案二:在StateNotifier内维护活跃任务状态,按需更新
如果不需要每秒精确刷新,可以计算每个任务的下一个状态切换时间(即将开始或即将结束的时间),然后设置Timer在该时间点更新状态,而非每秒刷新:
class TasksController extends StateNotifier<List<Task>> { TasksController() : super(const []) { init(); } Future<void> init() async { state = await GetTasksFromSomeRepo(); _scheduleNextUpdate(); } void _scheduleNextUpdate() { final now = DateTime.now(); // 找到所有任务中,距离当前最近的状态切换时间(开始或结束时间) DateTime? nextUpdateTime; for (final task in state) { if (task.start.isAfter(now)) { final diff = task.start.difference(now); if (nextUpdateTime == null || diff < nextUpdateTime.difference(now)) { nextUpdateTime = task.start; } } else if (task.end.isAfter(now)) { final diff = task.end.difference(now); if (nextUpdateTime == null || diff < nextUpdateTime.difference(now)) { nextUpdateTime = task.end; } } } if (nextUpdateTime != null) { final delay = nextUpdateTime.difference(now); Timer(delay, () { // 触发状态更新(这里可以直接更新state,即使数据没变,也会通知监听者) state = List.from(state); _scheduleNextUpdate(); }); } } // 当任务列表更新时,重新调度下一次更新 void updateTasks(List<Task> newTasks) { state = newTasks; _scheduleNextUpdate(); } }
然后在组件中,直接通过ref.watch(tasksControllerProvider)获取任务,同时在build中判断活跃状态:
class TaskItem extends StatelessWidget { final Task task; const TaskItem({super.key, required this.task}); @override Widget build(BuildContext context) { final isActive = task.start.isBefore(DateTime.now()) && task.end.isAfter(DateTime.now()); return ListTile( title: Text(task.title), trailing: isActive ? const Icon(Icons.radio_button_checked) : const Icon(Icons.radio_button_unchecked), ); } }
这种方案的优势是:
- 仅在任务状态即将变化时触发刷新,而非每秒刷新,性能开销更低
- 任务列表更新时自动重新计算下一次刷新时间,保证时效性
方案三:结合Riverpod的select功能,精准监听
如果使用方案一的时间Provider,可以结合select让单个Task组件仅在活跃状态变化时重建,进一步优化:
class TaskItem extends ConsumerWidget { final Task task; const TaskItem({super.key, required this.task}); @override Widget build(BuildContext context, WidgetRef ref) { final isActive = ref.watch(currentTimeProvider.select((time) { final current = time.value ?? DateTime.now(); return task.start.isBefore(current) && task.end.isAfter(current); })); return ListTile( title: Text(task.title), trailing: isActive ? const Icon(Icons.radio_button_checked) : const Icon(Icons.radio_button_unchecked), ); } }
select方法会仅当返回的isActive值变化时,才通知组件重建,避免时间每次变化都触发组件刷新。
内容的提问来源于stack exchange,提问作者Maciej Pszczolinski
相关产品推荐
相关产品推荐

