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

我有 CoursesTasks。每个 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));
  },