【问题标题】:Bloc, Flutter and NavigationBloc、颤振和导航
【发布时间】:2019-06-04 11:09:02
【问题描述】:

所以和大多数人一样,我是 Bloc 的新手,我会颤抖、飞镖和绕圈子。我用谷歌搜索过,查看了这里的帖子,但没有找到任何答案。

所以这是关于使用 bloc 和颤振的导航。以登录为例。所以有一个登录页面,后面有一个块,在某些时候有人按下按钮登录。

所以我们可以调用块中的一个函数来进行验证。我认为这违反了严格的方法,但我看到人们这样做。但是,如果登录成功,您如何导航到下一个屏幕?你不应该在一个集团中导航吗?

但是,如果该登录页面使用 StreamBuilder 来更改状态,那么您也不能在构建器中添加导航,可以吗?你不能返回导航,你只能返回小部件。

initstate 是您可以导航的地方,但是您是否可以在 initstate 中有一个流构建器来侦听 bloc 中的状态变化?

现在有点混乱,但我坚持,因为这是应该成为前进的方向......

谢谢 保罗

【问题讨论】:

  • 谢谢 Remi,我无法对那篇文章发表评论,但我一直在思考这个问题——所以我们必须使用有状态的小部件?我一直认为 bloc 远离有状态的小部件。然后,该集团仍然通过 Stream 传递值来确定何时导航。看起来有点笨拙,需要更多地使用它!谢谢!
  • BLoC 绝不会消除对 StatefulWidget 的需求
  • 理想情况下,您应该能够在 initState 中监听 BLoC.stream。但是我用这种方法面临另一个问题:提供 BLoC 的推荐方法是使用基于 Inherited Widget 的提供程序,使用调用 inheritFromWidgetOfExactType。这里的问题是不能在initState中调用inheritFromWidgetOfExactType,只能在didChangeDependencies中调用。但是框架会在各种实例上调用 didChangeDependencies,包括当您从页面中导航出去时。所以你可以进入一个循环:导航触发依赖关系的变化,反之亦然。
  • 查看this question 示例

标签: navigation flutter bloc


【解决方案1】:

为了消除 BLoC 是 前进道路的神话:没有完美的处理状态的方法。 每个状态管理架构都比其他的更好地解决了一些问题;总有权衡取舍,在决定架构时了解它们很重要。

通常,好的架构是实用的:它具有可扩展性和可扩展性,同时只需要最少的开销。 由于人们对实用性的看法不同,架构总是涉及到意见,所以下面我将就如何为您的应用采用 BLoC 阐述我个人的看法。

BLoC 是 Fl​​utter 中一种非常有前途的状态管理方法,因为它有一个标志性成分:流。 它们允许将 UI 与业务逻辑分离,并且它们与 Flutter-ish 的方法很好地配合,一旦它们过时就重建整个小部件子树。 所以很自然,BLoC 之间的每次通信都应该使用流,对吗?

+----+  Stream   +------+
| UI | --------> | BLoC |
|    | <-------- |      |
+----+   Stream  +------+

嗯,有点。

要记住的重要一点是状态管理架构是达到目的的手段;你不应该只是为了它而做事,而是要保持开放的心态,仔细评估每个选项的利弊。 我们将 BLoC 与 UI 分开的原因是 BLoC 不需要关心 UI 的结构——它只提供一些不错的简单流,而数据发生的任何事情都是 UI 的责任。

虽然流已被证明是一种将信息从 BLoC 传输到 UI 的绝妙方式,但它们在另一个方向上增加了不必要的开销: Streams 旨在传输连续的数据流(甚至在名称中也是如此),但大多数时候,UI 只需要触发 BLoC 中的单个事件。这就是为什么有时您会看到一些 Stream&lt;void&gt;s 或类似的 hacky 解决方案¹,只是为了遵守严格的 BLoC-y 做事方式。

此外,如果我们要基于来自 BLoC 的流推送新路由,则 BLoC 基本上会控制 UI 流——但我们试图阻止的正是直接控制 UI 和业务逻辑的代码!

这就是为什么一些开发人员(包括我)只是打破了完全基于流的解决方案并采用自定义方式从 UI 触发 BLoC 中的事件。 就个人而言,我只是使用方法调用(通常返回 Futures)来触发 BLoC 的事件:

+----+   method calls    +------+
| UI | ----------------> | BLoC |
|    | <---------------- |      |
+----+   Stream, Future  +------+

在这里,BLoC 为“实时”数据返回 Streams,并为方法调用返回 Futures。

让我们看看这对您的示例有何影响:

  • BLoC 可以提供用户是否登录的Stream&lt;bool&gt;,甚至可以提供Stream&lt;Account&gt;,其中Account 包含用户的帐户信息。
  • BLoC 还可以提供异步 Future&lt;void&gt; signIn(String username, String password) 方法,如果登录成功则不返回任何内容,否则会抛出错误。
  • UI 可以自行处理输入管理,并在按下登录按钮后触发类似以下内容:
try {
  setState(() => _isLoading = true); // This could display a loading spinner of sorts.
  await Bloc.of(context).signIn(_usernameController.text, _passwordController.text);
  Navigator.of(context).pushReplacement(...); // Push logged in screen.
} catch (e) {
  setState(() => _isLoading = false);
  // TODO: Display the error on the screen.
}

这样,您可以很好地分离关注点:

  • BLoC 确实只是在做它应该做的事情——处理业务逻辑(在本例中,是让用户登录)。
  • UI 只关心两件事:
    • 显示来自Streams 和的用户数据
    • 通过在 BLoC 中触发用户操作并根据结果执行 UI 操作来对用户操作做出反应。²

最后,我想指出,这只是一种可能的解决方案,它随着时间的推移在复杂应用程序中尝试不同的状态处理方式而演变。 了解关于状态管理如何工作的不同观点非常重要,因此我鼓励您深入研究该主题,也许可以通过观看 Google I/O 上的"Pragmatic State Management in Flutter" 会议。

编辑:刚刚在Brian Egan's architecture samples 中发现了这个架构,它被称为“Simple BLoC”。如果你想了解不同的架构,我真的建议你看看 repo。


¹当尝试为 BLoC 操作提供多个参数时,它变得更加丑陋 - 因为你需要定义一个包装类,只是为了将它传递给 Stream。

² 我确实承认在启动应用程序时它有点难看:您需要某种启动屏幕来检查 BLoC 的流并根据以下内容将用户重定向到适当的屏幕无论他们是否登录。发生该规则的异常是因为用户执行了一个操作——启动应用程序——但 Flutter 框架并不允许我们直接挂钩(据我所知,至少不是很优雅)。

【讨论】:

    【解决方案2】:

    BlocListener 是您可能需要的小部件。如果状态更改为(例如)LoginSuccess,则块侦听器可以调用通常的Navigate.of(context)。你可以找到example of BlocListener in action near the bottom of this page

    另一种选择是将回调传递给事件。

     BlocProvider.of<MyBloc>(context).add(MyEvent(
                  data: data,
                  onSuccess: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) {
                        return HomePage();
                      }),
                    );
                  }));
    

    【讨论】:

    • 链接现在消失了
    • 谢谢@C.Skjerdal 我现在已经修好了。
    • 这个例子作为一个主要流程。可能会发生这样的情况,在调用 onSuccess 时,小部件已经卸载,留下 context = null。
    • 这是一个不错的解决方案。我可以通过回调从集团路由到另一个页面
    【解决方案3】:

    首先:如果没有业务逻辑,则不需要进入 YourBloc 类。

    但有时某些用户的活动需要在 Bloc 类中执行某些逻辑,然后 Bloc 类必须决定下一步做什么:只需重建小部件 或 显示对话框,甚至导航到下一条路线。在这种情况下,您必须向 UI 发送一些 State 以完成操作。 然后又出现了一个问题:Bloc 发送 State 显示 toast 的时候,widget 怎么办?

    这是整个故事的主要问题。

    很多答案和文章推荐使用flutter_block。这个库有BlocBuilderBlocListener。通过这些课程,您可以解决一些问题,但不是 100%。

    在我的例子中,我使用了 BlocConsumer,它管理 BlocBuilderBlocListener 并提供了管理状态的绝妙方法。

    来自文档:

    BlocConsumer<BlocA, BlocAState>(
      listenWhen: (previous, current) {
        // return true/false to determine whether or not
        // to invoke listener with state
      },
      listener: (context, state) {
        // do stuff here based on BlocA's state
      },
      buildWhen: (previous, current) {
        // return true/false to determine whether or not
        // to rebuild the widget with state
      },
      builder: (context, state) {
        // return widget here based on BlocA's state
      }
    )
    

    正如您在 BlocConsumer 中看到的那样,您可以过滤状态:您可以轻松定义状态以重建小部件和状态以显示一些弹出窗口或导航到下一个屏幕。

    【讨论】:

      【解决方案4】:

      正如 felangel 在 Github 中提到的 issue,我们可以为此目的使用 BlocListner。

      BlocListener(
          bloc: BlocProvider.of<DataBloc>(context),
          listener: (BuildContext context, DataState state) {
              if (state is Success) {              
                  Navigator.of(context).pushNamed('/details');
              }              
          },
          child: BlocBuilder(
              bloc: BlocProvider.of<DataBloc>(context),
              builder: (BuildContext context, DataState state) {        
                  if (state is Initial) {
                      return Text('Press the Button');
                  }
                  if (state is Loading) {
                      return CircularProgressIndicator();
                  }  
                  if (state is Success) {
                      return Text('Success');
                  }  
                  if (state is Failure) {
                      return Text('Failure');
                  }
              },
          }
      )

      【讨论】:

      • 如果我返回并再次按下按钮以转到详细信息但集团抛出相同的状态“成功”(只是假设),侦听器不会触发任何 pushNamed 因为是相同的状态和以前一样,我该如何处理?
      猜你喜欢
      • 2020-07-14
      • 2019-12-16
      • 2021-11-24
      • 2020-10-14
      • 2020-10-10
      • 2019-07-01
      • 1970-01-01
      • 2020-08-22
      • 2018-10-11
      相关资源
      最近更新 更多