【问题标题】:Using Riverpod - how to properly implement automatic refresh/rebuild functionalty?使用 Riverpod - 如何正确实现自动刷新/重建功能?
【发布时间】:2022-12-09 20:55:43
【问题描述】:

我有一个简单的应用程序,其中包含 Tasks 列表。每个Task 都有时间限制。

@freezed
class Task with _$Task {
  const factory Task({
    @Default('') String title,
    required DateTime start,
    required DateTime end,
  }) = _Task;
}

然后我使用提供者显示任务,例如:

class TasksController extends StateNotifier<List<Task>> {
  TasksController()
      : super(const []) {
    init();
  }

  Future<void> init() async {
    state = await GetTasksFromSomeRepo();
  }
}

我想显示任务正在发生的指标,例如:

存在一个简单的解决方案,我每 1 秒重建一次 Task 的小部件的整个列表,使用:

Timer.periodic(Duration(seconds: 1), (Timer t) {
    setState(() {
        // this calls build method
    });
});

build方法本身中,我检查DateTime.now()是否在每个Task“开始/结束”范围内。 然而,这似乎是一种低效的方法。

问题:
  1. 如何以更有效的方式做到这一点,最好是在 Riverpod 的帮助下?

【问题讨论】:

  • 您的应用程序是否与后端通信?
  • @Axel 它不与任何 API 通信。要检查某事是否“正在进行”,它使用当地时间DateTime.now()

标签: flutter riverpod


【解决方案1】:

任务解析器.dart :

    enum TaskEvent {
      onProgrees,
      completed,
    }
    
    class TaskResolver {
    
      final Ref ref;
      TaskResolver(this.ref);
    
      Stream<TaskEvent> taskEvent() async* {

    // get from your provider,
    //ref.watch(taskController);

    //for simulation i just assign next day
    final taskDateCompleted = DateTime(2022, 12, 8);
    TaskEvent? current;
    while (true){
      final now = DateTime.now();

      if(current == null){
        //default value

        current = TaskEvent.onProgrees;
        yield current;
      }
      if(taskDateCompleted.isBefore(now) && current != TaskEvent.onProgrees){
        current = TaskEvent.onProgrees;
        yield TaskEvent.onProgrees;
      }
      if(taskDateCompleted.isAfter(now) && current != TaskEvent.completed){
        current = TaskEvent.completed;
        yield TaskEvent.completed;
      }
      await Future.delayed(const Duration(seconds: 1));
    }

  }

}
    
    final taskResolver = StreamProvider.autoDispose<TaskEvent>((ref) => TaskResolver(ref).taskEvent());

在 ui.dart 上:

class TaskPage extends ConsumerWidget {
  const TaskPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, ref) {
    final status = ref.watch(taskResolver);
    return status.when(
        data: (taskEvent){
          print(taskEvent);
          return Text(taskEvent.name);
        },
        error: (_,__)=> const Text('error'),
        loading: ()=> const Text('loading')
    );

  }
}

【讨论】:

    【解决方案2】:

    使用setState 的更高效解决方案,这里TaskContainer 小部件最多重建两次。

    class TaskContainer extends StatefulWidget {
      const TaskContainer({
        required this.task,
        Key? key,
      }) : super(key: key);
    
      @override
      State<TaskContainer> createState() => _TaskContainerState();
    
      final Task task;
    }
    
    class _TaskContainerState extends State<TaskContainer> {
      bool _inProgress = false;
    
      void updateProgress() {
        final now = DateTime.now();
    
        setState(() {
          _inProgress = now.compareTo(widget.task.start) >= 0 &&
              now.compareTo(widget.task.end) <= 0;
        });
      }
    
      @override
      void initState() {
        super.initState();
    
        updateProgress();
    
        final nowFromEpoch = DateTime
            .now()
            .microsecondsSinceEpoch;
        final timeToStart = widget.task.start.microsecondsSinceEpoch - nowFromEpoch;
        final timeToEnd = widget.task.end.microsecondsSinceEpoch - nowFromEpoch;
    
        if (timeToStart > 0) {
          Future.delayed(Duration(microseconds: timeToStart), updateProgress);
        }
        if (timeToEnd > 0) {
          Future.delayed(Duration(microseconds: timeToEnd), updateProgress);
        }
      }
    
      @override
      Widget build(BuildContext context) {
        final task = widget.task;
        final formatter = DateFormat('HH:mm');
    
        return Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            border: Border.all(color: Colors.black),
          ),
          padding: const EdgeInsets.all(10),
          child: Column(
            children: [
              Row(
                children: [
                  Text(task.title),
                  const SizedBox(
                    width: 20,
                  ),
                  if (_inProgress) const Text('In Progress...'),
                ],
              ),
              Row(
                children: [
                  Text(formatter.format(task.start)),
                  const SizedBox(
                    width: 20,
                  ),
                  Text(formatter.format(task.end)),
                ],
              )
            ],
          ),
        );
      }
    }
    

    用于测试它的示例页面。

    class Home extends StatelessWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final now = DateTime.now();
        final repository = <Task>[
          Task(
            title: 'First',
            start: now,
            end: DateTime(now.year, now.month, now.day, now.hour, now.minute + 1),
          ),
          Task(
            title: 'Second',
            start: DateTime(now.year, now.month, now.day, now.hour, now.minute + 1),
            end: DateTime(now.year, now.month, now.day, now.hour, now.minute + 2),
          ),
        ];
    
        return Scaffold(
          body: ListView.separated(
            itemBuilder: (context, index) => TaskContainer(task: repository[index]),
            separatorBuilder: (context, index) =>
            const SizedBox(
              height: 10,
            ),
            itemCount: repository.length,
          ),
        );
      }
    }
    

    编辑:使用 riverpod 实现
    您可以通过使用一系列将任务作为参数的提供程序来实现相同的目的。您的Task 类应该是不可变的并覆盖== operatorhashcode

    供应商:

    final taskProgressingProvider = StateNotifierProvider.family<TaskProgressingNotifier, bool, Task>((ref, task) {
      return TaskProgressingNotifier(task);
    });
    
    class TaskProgressingNotifier extends StateNotifier<bool> {
      TaskProgressingNotifier(this.task) : super(false) {
        updateProgress();
    
        final nowFromEpoch = DateTime.now().microsecondsSinceEpoch;
        final timeToStart = task.start.microsecondsSinceEpoch - nowFromEpoch;
        final timeToEnd = task.end.microsecondsSinceEpoch - nowFromEpoch;
    
        if (timeToStart > 0) {
          Future.delayed(Duration(microseconds: timeToStart), updateProgress);
        }
        if (timeToEnd > 0) {
          Future.delayed(Duration(microseconds: timeToEnd), updateProgress);
        }
      }
    
      final Task task;
    
      void updateProgress() {
        final now = DateTime.now();
    
        state = now.compareTo(task.start) >= 0 && now.compareTo(task.end) <= 0;
      }
    }
    

    TaskContainer转换为ConsumerWidget

    class TaskContainer extends ConsumerWidget {
      const TaskContainer({
        required this.task,
        Key? key,
      }) : super(key: key);
    
      final Task task;
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final formatter = DateFormat('HH:mm');
        final inProgress = ref.watch(taskProgressingProvider(task));
    
        return Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            border: Border.all(color: Colors.black),
          ),
          padding: const EdgeInsets.all(10),
          child: Column(
            children: [
              Row(
                children: [
                  Text(task.title),
                  const SizedBox(
                    width: 20,
                  ),
                  if (inProgress) const Text('In Progress...'),
                ],
              ),
              Row(
                children: [
                  Text(formatter.format(task.start)),
                  const SizedBox(
                    width: 20,
                  ),
                  Text(formatter.format(task.end)),
                ],
              )
            ],
          ),
        );
      }
    }
    

    【讨论】:

    • timeToStart 的这种方法真的很聪明,我 +1。但是我真的很想知道如何用 Riverpod 方法解决它。与您的方法一样,我们将状态保存在小部件(_inProgress 变量)中,而我希望提升状态。因此问题仍然存在。
    • @MaciejPszczolinski 我更新了我的答案以包含使用 riverpod 的实现。
    【解决方案3】:

    由于您不与任何 API 通信,因此正如您在问题中提到的那样,检查正在进行的事情的唯一方法是使用计时器。

    但是,您可以做的是,不调用 setState,而是通过访问 TasksController 更改 task 中的某些内容(您可以创建一个新变量 state)。

    如果正确实施,那应该会触发状态更改,即只重建正确的小部件,而不是整个小部件树。

    【讨论】:

    • 那么在使用 Riverpod 时在控制器内部实现一个计时器是否正确?我从未见过任何类似的例子,因此我很困惑
    • 是的,我认为这将是您所处情况的最佳解决方案。但是,我会在 initState 方法中的小部件类中创建计时器
    • 为什么在小部件里面?难道不是国家的一部分就属于国家管理吗?虽然小部件只是演示的一部分?
    • 正确,但如果小部件未启动,则检查是否需要更新 UI 毫无意义。因此,您实际上只需要计时器在小部件处于活动状态时工作。
    猜你喜欢
    • 1970-01-01
    • 2016-04-05
    • 1970-01-01
    • 1970-01-01
    • 2013-05-07
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多