Flutter BLoC: 在build()方法中的StreamBuilder中使用Navigator.pop

14

我正在遵循BLoC模式并订阅流,然后在build方法中对状态更改做出反应。当数据加载完成时,我想关闭屏幕。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bloc'),
      ),
      body: SafeArea(
        child: StreamBuilder<UserState>(
          stream: _userBloc.user,
          initialData: UserInitState(),
          builder: (context, snapshot) {
            if (snapshot.data is UserInitState) {
              return _buildInit();
            }
            if (snapshot.data is UserDataState) {
              Navigator.pop(context, true);
              return Container();
            }
            if (snapshot.data is UserLoadingState) {
              return _buildLoading();
            }
          },
        ),
      ),
    );
  } 

当我在build()方法中执行Navigator.pop(context, true);时,我得到:

I/flutter ( 4360): ══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 4360): The following assertion was thrown while notifying status listeners for AnimationController:
I/flutter ( 4360): setState() or markNeedsBuild() called during build.
I/flutter ( 4360): This Overlay widget cannot be marked as needing to build because the framework is already in the
I/flutter ( 4360): process of building widgets. A widget can be marked as needing to be built during the build phase
I/flutter ( 4360): only if one of its ancestors is currently building. This exception is allowed because the framework
I/flutter ( 4360): builds parent widgets before children, which means a dirty descendant will always be built.
I/flutter ( 4360): Otherwise, the framework might not visit this widget during this build phase.

BLoC模式中处理这种情况的正确方法是什么?

我想到的其中一种解决方案是在initState()中开始监听流。在这种情况下,我需要broadcast()我的流,因为我有两个订阅者。

还有更好的解决方案吗?


你已经提到了完美的解决方案。 - creativecreatorormaybenot
可能是以下问题的重复:当BLOC中的流值更改时导航到新屏幕 - SoloWolf93
3个回答

17
我认为我为你找到了一个解决方案。(请检查一下)
让你的代码看起来像这样:
Widget build(BuildContext context) {
    // other stuff

    if (snapshot.data is UserDataState) {
      myCallback(() {
        Navigator.pop(context, true);
      });
    }

    // other stuff
}
// after build method (but still in the same class,...) write below method

void myCallback(Function callback) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      callback();
    });
}

希望能对你有所帮助。只需尝试并在此处报告,以帮助其他人!

来源(flutter_bloc登录中的medium文章)

说明


我们可以在BlocBuilder中使用它吗? - Nadeem Shaikh

1
bool hasPop = false;

  @override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Bloc'),
    ),
    body: SafeArea(
      child: StreamBuilder<UserState>(
        stream: _userBloc.user,
        initialData: UserInitState(),
        builder: (context, snapshot) {
          if (snapshot.data is UserInitState) {
            return _buildInit();
          }
          if (snapshot.data is UserDataState) {
            if(!hasPop){
               hasPop = true;
               Navigator.pop(context, true);
             }
            
            return Container();
          }
          if (snapshot.data is UserLoadingState) {
            return _buildLoading();
          }
        },
      ),
    ),
  );
} ```

请问您能解释一下这段代码吗?谢谢! - corn on the cob
StreamBuilder 中的每个更改都会触发两次。Navigator.pop 不能被调用两次。 - mario francois
@GoldenLion 这不是我的问题,所以我不能给你答案。这只是类似于 https://github.com/felangel/bloc/tree/master/examples/flutter_shopping_cart/lib/cart/bloc 的 bloc 模式。 - mario francois
我使用implements并为不同状态响应创建了一个子类,通过bloc实现。 - Golden Lion

0

我可以想象出三种可能的解决方案:

1)在我看来,最好重新构建您的小部件。据我所见,您想要一个“加载屏幕”..我认为这不必是自己的导航项,而只是另一个小部件。

也就是说,您可以将StreamBuilder向上推一级..这样您的构建器方法看起来像:

if (!snapshot.hasData) {
  return LoadingScreen(snapshot);
}
// Whatever you do with your data.

2) 我认为我个人会创建一个 StatefulWidget,在 initState() 中手动监听流并手动调用 setState()。不需要使用 StreamBuilder

3) 作为一个疯狂的解决方法,你可能可以在你的构建器中使用 Future(() { Navigator.of(context).pop(); });。(可能需要使用 build 方法的 context,而不是构建器的 context。但我不建议使用这种解决方案)


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