Flutter BLoC mapEventToState 仅在第一次事件时被调用,而不会在下次触发该事件时被调用
Flutter BLoC mapEventToState gets called only the first time for an event and not called each next time that event is fired
我有 Courses
和 Tasks
。每个 Course
有许多 Tasks
。这就是为什么我在应用程序中使用不同的屏幕来显示课程列表,并且在点击课程后,我将导航到下一个屏幕 - 任务列表。这是我的 onTap
课程列表方法:
onTap: () {
TasksPageLoadedEvent pageLoadedEvent =
TasksPageLoadedEvent(
courseId: state.courses[index].id,
truckNumber: this.truckNumber,
);
serviceLocator<TaskBloc>().add(pageLoadedEvent);
Routes.sailor(
Routes.taskScreen,
params: {
Routes.courseNumber:
state.courses[index].courseNumber,
Routes.truckNumber: this.truckNumber,
Routes.courseId: state.courses[index].id,
},
);
}
我创建了一个 TasksPageLoadedEvent
,将其传递给 TaskBloc
并导航到“任务”页面。
这是 TaskBloc
以及它如何处理映射事件 - 状态:
@override
Stream<TaskState> mapEventToState(
TaskEvent event,
) async* {
if (event is TasksLoadingEvent) {
yield TasksLoadingState();
} else if (event is TasksReloadingErrorEvent) {
yield TasksErrorState();
} else if (event is TasksFetchedFailureEvent) {
yield TaskFetchedStateFailureState(error: event.failure);
} else if (event is TasksPulledFromServerEvent) {
yield TasksPulledFromServerState(
truckNumber: event.truckNumber,
courseNumber: event.courseNumber,
courseId: event.courseId,
);
} else if (event is TasksPageLoadedEvent) {
yield TasksLoadingState();
final networkInfoEither = await this.getNetworkInfoQuery(NoQueryParams());
yield* networkInfoEither.fold((failure) async* {
yield TasksErrorState();
}, (success) async* {
if (success) {
final getTasksEither = await getTasksQuery(
GetTasksParams(
truckNumber: event.truckNumber,
courseId: event.courseId,
),
);
yield* getTasksEither.fold((failure) async* {
yield TaskFetchedStateFailureState(error: "coursesDatabaseError");
}, (result) async* {
if (result != null) {
yield TasksFetchedState(tasks: result);
} else {
yield TaskFetchedStateFailureState(
error: "coursesFetchFromDatabaseError");
}
});
} else {
yield TasksNoInternetState();
}
});
}
}
当我导航到“任务”页面时,BlocBuilder
检查状态并相应地处理构建。我有返回课程页面的返回功能:
onPressed: () {
serviceLocator<CourseBloc>().add(
CoursesPageLoadedEvent(truckNumber: this.truckNumber),
);
Navigator.of(context).pop(true);
},
这会触发上一页的类似事件并重新加载。
如果我想去另一门课程并查看其任务,就会出现我面临的问题。如果我点击列表中的另一个项目并因此触发一个新的 TasksPageLoadedEvent
(具有新属性),mapEventToState()
根本不会被调用。
我之前遇到过与 BLoC 类似的问题,但它们是关于 BlocListener
和扩展 Equatable
的状态。这就是为什么我的事件没有扩展 Equatable
(尽管我不确定这是否是这里的问题)。但是还是没有任何反应。
这是我的活动:
abstract class TaskEvent {
const TaskEvent();
}
class TasksPageLoadedEvent extends TaskEvent {
final String truckNumber;
final int courseId;
TasksPageLoadedEvent({
this.truckNumber,
this.courseId,
});
}
class TasksFetchedFailureEvent extends TaskEvent {
final String failure;
TasksFetchedFailureEvent({
this.failure,
});
}
class TasksLoadingEvent extends TaskEvent {}
class TasksReloadingErrorEvent extends TaskEvent {}
class TasksPulledFromServerEvent extends TaskEvent {
final String courseNumber;
final String truckNumber;
final int courseId;
TasksPulledFromServerEvent({
@required this.courseNumber,
@required this.truckNumber,
@required this.courseId,
});
}
我应该如何使用每个页面的两个 BLoC 来处理两个页面之间的来回?
好的,我自己找到了答案!
当然,正如费德里克乔纳森所暗示的那样,这个问题就是集团的例子。我正在使用由 flutter 包 get_it 创建的单例实例。如果您正在实施依赖注入(例如,对于干净的架构),这将非常有用。
所以一个实例就是问题所在。
幸运的是这个包实现了整洁的方法resetLazySingleton<T>
。
返回时调用它会重置该小部件中使用的集团。因此,当我再次导航到“任务”页面时,我正在使用该集团的相同但已重置的实例。
Future<bool> _onWillPop() async {
serviceLocator.resetLazySingleton<TaskBloc>(
instance: serviceLocator<TaskBloc>(),
);
return true;
}
我希望这个答案能帮助遇到单例、依赖注入以及在带有 bloc 的 flutter 应用程序中来回移动的麻烦的人。
对于遇到类似问题的其他人:
如果您正在侦听存储库流并循环遍历发出的对象,则会导致 mapEventToState
被阻塞。因为循环永远不会结束。
Stream<LoaderState<Failure, ViewModel>> mapEventToState(
LoaderEvent event) async* {
yield* event.when(load: () async* {
yield const LoaderState.loadInProgress();
await for (final Either<Failure, Entity> failureOrItems in repository.getAll()) {
yield failureOrItems.fold((l) => LoaderState.loadFailure(l),
(r) => LoaderState.loadSuccess(mapToViewModel(r)));
}
});
}
你应该做什么而不是 await for
流,听流然后引发另一个事件,然后处理事件:
watchAllStarted: (e) async* {
yield const NoteWatcherState.loadInProgress();
_noteStreamSubscription = _noteRepository.watchAll().listen(
(failureOrNotes) =>
add(NoteWatcherEvent.notesReceived(failureOrNotes)));
},
notesReceived: (e) async* {
yield e.failureOrNotes.fold(
(failure) => NoteWatcherState.loadFailure(failure),
(right) => NoteWatcherState.loadSuccess(right));
},
我有 Courses
和 Tasks
。每个 Course
有许多 Tasks
。这就是为什么我在应用程序中使用不同的屏幕来显示课程列表,并且在点击课程后,我将导航到下一个屏幕 - 任务列表。这是我的 onTap
课程列表方法:
onTap: () {
TasksPageLoadedEvent pageLoadedEvent =
TasksPageLoadedEvent(
courseId: state.courses[index].id,
truckNumber: this.truckNumber,
);
serviceLocator<TaskBloc>().add(pageLoadedEvent);
Routes.sailor(
Routes.taskScreen,
params: {
Routes.courseNumber:
state.courses[index].courseNumber,
Routes.truckNumber: this.truckNumber,
Routes.courseId: state.courses[index].id,
},
);
}
我创建了一个 TasksPageLoadedEvent
,将其传递给 TaskBloc
并导航到“任务”页面。
这是 TaskBloc
以及它如何处理映射事件 - 状态:
@override
Stream<TaskState> mapEventToState(
TaskEvent event,
) async* {
if (event is TasksLoadingEvent) {
yield TasksLoadingState();
} else if (event is TasksReloadingErrorEvent) {
yield TasksErrorState();
} else if (event is TasksFetchedFailureEvent) {
yield TaskFetchedStateFailureState(error: event.failure);
} else if (event is TasksPulledFromServerEvent) {
yield TasksPulledFromServerState(
truckNumber: event.truckNumber,
courseNumber: event.courseNumber,
courseId: event.courseId,
);
} else if (event is TasksPageLoadedEvent) {
yield TasksLoadingState();
final networkInfoEither = await this.getNetworkInfoQuery(NoQueryParams());
yield* networkInfoEither.fold((failure) async* {
yield TasksErrorState();
}, (success) async* {
if (success) {
final getTasksEither = await getTasksQuery(
GetTasksParams(
truckNumber: event.truckNumber,
courseId: event.courseId,
),
);
yield* getTasksEither.fold((failure) async* {
yield TaskFetchedStateFailureState(error: "coursesDatabaseError");
}, (result) async* {
if (result != null) {
yield TasksFetchedState(tasks: result);
} else {
yield TaskFetchedStateFailureState(
error: "coursesFetchFromDatabaseError");
}
});
} else {
yield TasksNoInternetState();
}
});
}
}
当我导航到“任务”页面时,BlocBuilder
检查状态并相应地处理构建。我有返回课程页面的返回功能:
onPressed: () {
serviceLocator<CourseBloc>().add(
CoursesPageLoadedEvent(truckNumber: this.truckNumber),
);
Navigator.of(context).pop(true);
},
这会触发上一页的类似事件并重新加载。
如果我想去另一门课程并查看其任务,就会出现我面临的问题。如果我点击列表中的另一个项目并因此触发一个新的 TasksPageLoadedEvent
(具有新属性),mapEventToState()
根本不会被调用。
我之前遇到过与 BLoC 类似的问题,但它们是关于 BlocListener
和扩展 Equatable
的状态。这就是为什么我的事件没有扩展 Equatable
(尽管我不确定这是否是这里的问题)。但是还是没有任何反应。
这是我的活动:
abstract class TaskEvent {
const TaskEvent();
}
class TasksPageLoadedEvent extends TaskEvent {
final String truckNumber;
final int courseId;
TasksPageLoadedEvent({
this.truckNumber,
this.courseId,
});
}
class TasksFetchedFailureEvent extends TaskEvent {
final String failure;
TasksFetchedFailureEvent({
this.failure,
});
}
class TasksLoadingEvent extends TaskEvent {}
class TasksReloadingErrorEvent extends TaskEvent {}
class TasksPulledFromServerEvent extends TaskEvent {
final String courseNumber;
final String truckNumber;
final int courseId;
TasksPulledFromServerEvent({
@required this.courseNumber,
@required this.truckNumber,
@required this.courseId,
});
}
我应该如何使用每个页面的两个 BLoC 来处理两个页面之间的来回?
好的,我自己找到了答案!
当然,正如费德里克乔纳森所暗示的那样,这个问题就是集团的例子。我正在使用由 flutter 包 get_it 创建的单例实例。如果您正在实施依赖注入(例如,对于干净的架构),这将非常有用。
所以一个实例就是问题所在。
幸运的是这个包实现了整洁的方法resetLazySingleton<T>
。
返回时调用它会重置该小部件中使用的集团。因此,当我再次导航到“任务”页面时,我正在使用该集团的相同但已重置的实例。
Future<bool> _onWillPop() async {
serviceLocator.resetLazySingleton<TaskBloc>(
instance: serviceLocator<TaskBloc>(),
);
return true;
}
我希望这个答案能帮助遇到单例、依赖注入以及在带有 bloc 的 flutter 应用程序中来回移动的麻烦的人。
对于遇到类似问题的其他人:
如果您正在侦听存储库流并循环遍历发出的对象,则会导致 mapEventToState
被阻塞。因为循环永远不会结束。
Stream<LoaderState<Failure, ViewModel>> mapEventToState(
LoaderEvent event) async* {
yield* event.when(load: () async* {
yield const LoaderState.loadInProgress();
await for (final Either<Failure, Entity> failureOrItems in repository.getAll()) {
yield failureOrItems.fold((l) => LoaderState.loadFailure(l),
(r) => LoaderState.loadSuccess(mapToViewModel(r)));
}
});
}
你应该做什么而不是 await for
流,听流然后引发另一个事件,然后处理事件:
watchAllStarted: (e) async* {
yield const NoteWatcherState.loadInProgress();
_noteStreamSubscription = _noteRepository.watchAll().listen(
(failureOrNotes) =>
add(NoteWatcherEvent.notesReceived(failureOrNotes)));
},
notesReceived: (e) async* {
yield e.failureOrNotes.fold(
(failure) => NoteWatcherState.loadFailure(failure),
(right) => NoteWatcherState.loadSuccess(right));
},