flutter - 触发并等待不同 BLoC 中的项目创建
问题描述
对于我想要实现的简单事情,我下面的方法感觉很复杂:
我有一个Task
由 a 管理的 s列表TaskBloc
。UI 列出所有任务并为execute
每个任务提供一个按钮。对于每次单击该按钮,我想创建并存储一个Action
(基本上是任务执行发生时的时间戳)并在创建操作时显示一个微调器。我有一个ActionBloc
管理操作(例如创建或获取每个任务的历史记录)。
我很困惑如何设置 BLoC 之间的通信。
到目前为止,这是我的方法。
只是保存所有存储操作的ActionsState
列表。
class ActionsState extends Equatable {
final List<Action> actions;
// ... copyWith, constructors etc.
}
Action
是一个简单的 PODO 类,包含一个id
and timestamp
。
TheActionsBloc
能够创建Action
s 来响应它的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
通过将相应的事件添加到ActionBloc
as 对 的响应来触发创建很简单TaskExecutionStarted
:
Stream<TaskState> mapEventToState(TaskEvent event) async* {
// ...
// ... set executeStatus: Status.loading as shown above ...
// trigger creating a new action
actionsBloc.add(ActionCreationStarted(taskId: taskId));
// ...
当然,我的目标是明确分离关注点、单一事实来源和其他关于应用程序状态结构的意外复杂性的潜在来源——但总体而言,这种方法(在工作之前仍然表示问题未解决)感觉有点复杂,只是为了存储每个时间戳任务的动作并跟踪动作创建请求。
感谢您到目前为止阅读(!),我很高兴有关该用例的干净架构的提示。
解决方案
所以我们最终做的是以下内容:
引入一个表示最后创建操作的状态的lastCreatedState
in 。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
处理。
虽然内部结构稍微复杂一些,但它可以将事物分开并使使用集团变得轻而易举。
推荐阅读
- black-box - “未知身份”黑盒任务解决方案?
- html - 没有出现图例,但代码中没有错误(?)
- apache-nifi - Apache NiFi 尾文件处理器不适用于大型日志文件
- c++ - 如何根据类模板参数禁用成员函数?
- python-3.x - 将数字拆分为 2 个特定范围的数字列表
- python - 有没有办法在 json.loads 方法中获取双引号
- c++ - _Pairib': 不是 'std::map,std::allocator>> 的成员
- artifactory - 如何删除工件中的多个文件
- r - 使用 kable 将字符串拆分为多列
- vb.net - xaml 第 0 行“找不到类型的主窗口”