从其他Bloc中监听Flutter的Bloc状态

27
你好,我正在尝试从另一个区块监听区块的状态。 我正在使用这个包 https://pub.dev/packages/bloc
从我的 UserBloc 中,我想要监听 AuthBloc,并且当它处于 AuthenticationAuthenticated 状态时,UserBloc 应该触发一个事件。
final UserRepository userRepository;
final authBloc;
StreamSubscription authSub;
UserBloc({ @required this.userRepository, @required this.authBloc}) {
    authSub = authBloc.listen((stateAuth) {

      //here is my problem because stateAuth, even is AuthenticationAuthenticated it return always false.
      if (stateAuth is AuthenticationAuthenticated) {
        this.add(GetUser())  ;
      }
    });
  }

@override
  Future<void> close() async {
    authSub?.cancel();
    super.close();
  }

目前我有一个问题: 在调试时,当我尝试打印stateAuth时,它返回:
stateAuth = {AuthenticationAuthenticated} AuthenticationAuthenticated
   props = {_ImmutableList} size = 0

但是stateAuth是AuthenticationAuthenticated,总是返回false。
有没有办法从其他Bloc类中监听blocState?

1
你成功地做到了这件事吗?我正在尝试实现类似的东西。 - user3836415
5个回答

21

实际上,在bloc库的一个示例中,他们从另一个Bloc(FilteredTodosBloc)监听Bloc(TodosBloc)。

class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
  final TodosBloc todosBloc;
  StreamSubscription todosSubscription;

  FilteredTodosBloc({@required this.todosBloc}) {
    todosSubscription = todosBloc.listen((state) {
      if (state is TodosLoadSuccess) {
        add(TodosUpdated((todosBloc.state as TodosLoadSuccess).todos));
      }
    });
  }
...

你可以在这里查看此示例的说明。

8
这篇文章已被标记为过时,最新的方法不再使用这种方法,也不知道它是否仍然适用。 - thanhbinh84
补充上面的评论,现在可能的正确答案是下面这个评论:https://stackoverflow.com/a/72496719/4943676 - undefined

19
回答Sampir的问题,是的,你是对的,但有时候你可能想用另一种方式来做。Bloc 是为他人管理事件的东西。如果你正在处理 ui 事件,你的 bloc 就会为你的 ui 管理它们,但如果你也在处理其他类型的事件(例如位置事件或其他流事件),你可以拥有一个管理 UI 事件的 bloc 和另一个管理其他类型事件的 bloc(例如蓝牙连接)。因此,第一个 bloc 必须听从第二个 bloc(例如因为它正在等待建立蓝牙连接)。想象一个使用大量传感器的应用程序,每个传感器都有自己的数据流,你将拥有一系列必须合作的 Bloc。
你几乎可以在任何地方向 bloc 添加监听器。使用 StreamSubscription,你可以添加监听器到任何类型的流,甚至是另一个 bloc 中的流。Bloc 必须具有公开其流的方法,以便你可以监听它。
一些代码(我使用 flutter_bloc - flutter_bloc 具有多个提供程序,但这只是示例):
class BlocA extends Bloc<EventA, StateA> {

  final BlocB blocB;
  StreamSubscription subscription;

  BlocA({this.blocB}) {
    if (blocB == null) return;
    subscription = blocB.listen((stateB) {
      //here logic based on different children of StateB
    });
  }

  //...

}

class BlocB extends Bloc<EventB, StateB> {
   //here BlocB logic and activities
}

我有一个问题,我正在做与蓝牙连接类似的事情,但对于WiFi和移动网络,我使用了块。我想让这个块监听连接状态的变化并重建用户界面。但是在这种方式下,我只使用了一个连接类,要触发构建,我必须使用UI事件。我想做的是从后台线程触发监视连接变化的事件。 - UN..D
请注意,不建议在一个 bloc 中监听其他 bloc,因为这会在它们之间创建紧密的耦合。您可以通过使用 BlocB 的 BlocListener 包装 BlocA 的 BlocProvider,并为 BlocB 的每个事件添加 BlocA 事件来避免这种情况。请参见 https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication - stefanS
现在可能的正确答案是来自下面这条评论:https://stackoverflow.com/a/72496719/4943676 - undefined

12

你可能不希望你的模块依赖于另一个直接的模块,而是要通过展示层来连接你的模块。

你可以使用BlocListener来监听一个模块,并在第一个模块改变时向另一个模块添加事件。

class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocListener<WeatherCubit, WeatherState>(
      listener: (context, state) {
        // When the first bloc's state changes, this will be called.
        //
        // Now we can add an event to the second bloc without it having
        // to know about the first bloc.
        BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
      },
      child: TextButton(
        child: const Text('Hello'),
        onPressed: () {
          BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
        },
      ),
    );
  }
}

上述代码可以防止 SecondBloc 需要了解 FirstBloc 的细节,从而促进松耦合。

请查看官方文档以获取更多信息。


3
这应该是正确的答案,其他的已经过时了。 - kokemomuke
以下是来自@Vingtoft的正确答案 - Richard Sipher
1
在我看来,将业务逻辑放在展示层的“最佳实践”方法是可怕的。这个新的答案要好得多https://stackoverflow.com/a/76900070/1058292。应该将业务逻辑分离出来,这样我们就可以测试组件之间的交互,而不依赖于混乱的前端代码。链接的解决方案还意味着当BlockA只需要一小部分BlockB时,BlockA不会依赖于整个BlockB。这有助于模拟和测试。前端代码中的监听器应该保留给前端工作,比如隐藏和显示snackbar。 - undefined

6

区块源代码的最新更新需要对解决方案进行小改动。

现在您必须监听 bloc/cubit 的 stream 属性,请参见以下示例。

class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
  final TodosBloc todosBloc;
  StreamSubscription todosSubscription;

  FilteredTodosBloc({@required this.todosBloc}) {
    todosSubscription = todosBloc.stream.listen((state) {
    //                             ^^^^^
      if (state is TodosLoadSuccess) {
        add(TodosUpdated((todosBloc.state as TodosLoadSuccess).todos));
      }
    });
  }
...

1
那些建议将一个 bloc 传递给另一个 bloc 的答案在我看来并不是很好,因为它们会在这两个 bloc 之间创建紧密的耦合。
我认为那些建议将第一个 bloc 的状态传递给依赖的 bloc 的答案更好,并且可以通过以下方法改进:使用自定义类/记录的流传递给依赖的 bloc,这样你可以将这两个 bloc 解耦,并且只监听你感兴趣的第一个 bloc 状态的部分,在第二个 bloc 中处理。
class PartialState{ // partial state of bloc B that bloc A is interested in (it can be also the whole state, in which case you pass below the state stream directly instead of a stream of this class)
  final SomeParamFromBlocBState param1;

  final AnotherParamFromBlocBState? param2;

  // constructor...
}

class BlocA extends Bloc<EventA, StateA> {

  final PartialState partialState;
  StreamSubscription<PartialState> subscription;

  BlocA(Stream<PartialState> stream) {
    subscription = stream.listen((partialState) {
      // do something
    });
  }

//...

}

class BlocB extends Bloc<EventB, StateB> {
  //here BlocB logic and activities
}

// somewhere in your app
void main(){
  final blocB = BlocB();
  final blocA = BlocA(blocB.state.map((state) => PartialState(param1: state.param1, param2: state.param2)));
}

注意:

  • 不要忘记在 bloc 的 close 方法中关闭订阅。
  • 当从另一个 bloc 监听一个 bloc 的状态时要小心,因为如果你在某个 build 方法中定义了你的 blocs,并且提供流的 bloc 重新构建,而依赖的 bloc 没有构建,那么依赖的 bloc 可能会处于错误的状态,因为它监听的流已经无效了。
  • 有时候使用 BlocListener 是可以的,但有时候不行。例如:假设你想监听 BlocB 的某个状态,并调用在 BlocA 中定义的 onBlocBChanged 方法,如果你只需要在你的小部件中执行一次这样的操作,那么可以接受,但如果你需要在多个地方调用它,你可能会忘记在某个地方调用(即忘记放置一个 bloc listener),在这种情况下,更好的方法是通过上述方法集中调用。

1
在我看来,将业务逻辑放在展示层的“最佳实践”方法是可怕的。这种方式更好。我们应该将业务逻辑分离出来,这样我们可以测试组件之间的交互,而不依赖于混乱的前端代码。这个解决方案还意味着当BlockA只需要一小部分BlockB时,它不会依赖于整个BlockB。 - undefined
@atreeon 没错,如果有人想对自己的代码块进行单元测试,这个解决方案非常完美。我会编辑答案,同时提到这个重要的点,这个点之所以存在,完全是因为这个解决方案所提供的解耦。 - undefined
状态对象没有map方法。如何解决? - undefined
@Terriermon 你可以自己实现 map,或者使用一些库,比如 Freezed - undefined
@Terriermon 我写了一个名为 https://pub.dev/packages/morphy 的库,它可以实现多态的 copywith 和多态的 json 序列化。这个对你有帮助吗? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接