You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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的起止时间范围内,但该方案效率较低。

问题:

  1. 如何借助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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 04:40:24