首页 > 解决方案 > 触发并等待不同 BLoC 中的项目创建

问题描述

对于我想要实现的简单事情,我下面的方法感觉很复杂:

我有一个Task由 a 管理的 s列表TaskBloc。UI 列出所有任务并为execute每个任务提供一个按钮。对于每次单击该按钮,我想创建并存储一个Action(基本上是任务执行发生时的时间戳)并在创建操作时显示一个微调器。我有一个ActionBloc管理操作(例如创建或获取每个任务的历史记录)。

我很困惑如何设置 BLoC 之间的通信。


到目前为止,这是我的方法。

只是保存所有存储操作的ActionsState列表。

class ActionsState extends Equatable {
  final List<Action> actions;
  // ... copyWith, constructors etc.
}

Action是一个简单的 PODO 类,包含一个idand timestamp

TheActionsBloc能够创建Actions 来响应它的ActionCreationStarted事件(持有 a int taskId)。由于Action创建是在异步隔离中执行的,因此还有一些事件ActionCreationSucceeded,并且ActionCreationFailed在请求完成后由隔离添加。两者都持有Action已创建或创建失败的内容。

TaskState:_

class TaskState extends Equatable {
  final Map<int, Task> tasks;
  // ... copyWith, constructors, etc.

executeStatus在模型中添加了一个来Task跟踪任务列表中创建请求的状态(特定任务不能并行执行多次,只能按顺序执行,而不同的任务可以并行执行):

enum Status { initial, loading, success, error }

class Task extends Equatable {
  final int id;
  final Status executeStatus;
  // ...
}

我为以下事件添加了事件TaskBloc

class TaskExecutionStarted extends TaskEvent {
  final int taskId;
  // ...
}
class TaskExecutionSucceeded extends TaskEvent {
  final int taskId;
  // ...
}
class TaskExecutionFailed extends TaskEvent {
  final int taskId;
  // ...
}

TaskBloc我实现了mapEventToState新事件以根据事件设置任务状态,例如TaskExecutionStarted

Stream<TaskState> mapEventToState(TaskEvent event) async* {
  // ...
  if (event is TaskExecutionStarted) {
    final taskId = event.taskId;
    Task task = state.tasks[taskId]!;
    yield state.copyWith(
      tasks: {
        ...state.tasks,
        taskId: task.copyWith(executeStatus: Status.loading),
      },
    );
  }
  // ...
}

到目前为止,这使 UI 能够为每个任务显示一个微调器,ActionBloc还不知道它应该Action为该任务记录一个新的,并且TaskBloc不知道何时停止显示微调器。


问题

现在我迷失的部分是我需要实际触发ActionBloc来创建一个动作并在之后获取一个TaskExecutionSucceeded(或...Failed)事件。我考虑过在 上使用监听器ActionsBloc,但它只提供状态而不是事件ActionsBloc(我需要对ActionCreationSucceeded事件做出反应,但监听其他集团的事件感觉像是一种反模式(?!)和我什至不知道如何设置它)。

问题的核心是,我可能会监听ActionsBloc状态,但我不知道如何区分需要触发TaskExecutionSucceeded事件的状态的哪些动作。

无论如何,我给了TaskBloc一个参考ActionsBloc

class TaskBloc extends Bloc<TaskEvent, TaskState> {
  final ActionsBloc actionsBloc;
  late final StreamSubscription actionsSubscription;
  // ...
  TaskBloc({
    // ...
    required this.actionsBloc,
  }) : super(TaskState.initial()) {
    actionsSubscription = actionsBloc.listen((state) {
      /* ... ??? ... Here I don't know how to distinguish for which actions of the state
           I would somehow need to trigger a `TaskExecutionSucceeded` event. */
    });
  };
  // ...
}

为了完整起见,Action通过将相应的事件添加到ActionBlocas 对 的响应来触发创建很简单TaskExecutionStarted

Stream<TaskState> mapEventToState(TaskEvent event) async* {
  // ...
  // ... set executeStatus: Status.loading as shown above ...
  // trigger creating a new action
  actionsBloc.add(ActionCreationStarted(taskId: taskId));
  // ...

当然,我的目标是明确分离关注点、单一事实来源和其他关于应用程序状态结构的意外复杂性的潜在来源——但总体而言,这种方法(在工作之前仍然表示问题未解决)感觉有点复杂,只是为了存储每个时间戳任务的动作并跟踪动作创建请求。

感谢您到目前为止阅读(!),我很高兴有关该用例的干净架构的提示。

标签: flutterflutter-bloc

解决方案


所以我们最终做的是以下内容:

引入一个表示最后创建操作的状态的lastCreatedStatein 。ActionsState

当任务执行发生时,我们不会一直监听ActionsBloc它的状态,并记住每个事件的监听器。

一旦我们的ActionsBloc lastCreatedState状态发生变化,表明我们的任务成功或失败,我们就会移除监听器并对它做出反应。

类似这样的东西:

/// When a task is executed, we trigger an action creation and wait for the
/// [ActionsBloc] to signal success or failure for that task.
/// The mapping maps taskId => subscription.
final Map<int, StreamSubscription> _actionsSubscription = {};

Stream<TaskState> mapEventToState(TaskEvent event) async* {

  // ...

  // trigger creation of an action
  actionsBloc.add(ActionCreationStarted(taskId: taskId));
  // listen to the result
  // remove any previous listeners
  if (_actionsSubscription[taskId] != null) {
    await _actionsSubscription[taskId]!.cancel();
  }
  StreamSubscription<ActionsState>? listener;
  listener = actionsBloc.stream.listen((state) async {
    final status = state.lastCreatedState?.status;
    final doneTaskId = state.lastCreatedState?.action?.taskId;
    if (doneTaskId == taskId &&
        (status == Status.success || status == Status.error)) {
      await listener?.cancel(); // stop listening
      add(TaskExecutionDone(taskId: taskId, status: status!));
    }
  });
  _actionsSubscription[taskId] = listener;
}

@override
Future<void> close() {
  _actionsSubscription.values.forEach((s) {
    s.cancel();
  });
  return super.close();
}

它并不完美:它需要污染,ActionsState并且需要在所有侦听器完成(或至少有其他确保状态在创建时水合和同步的其他东西)和污染状态之前不被TaskBloc处理

虽然内部结构稍微复杂一些,但它可以将事物分开并使使用集团变得轻而易举。


推荐阅读