BLoC 中的逻辑应该如何触发一次性 UI 警报,例如对话框或快餐栏?

How should logic in a BLoC trigger a one-time UI alert, like a dialog or snackbar?

当使用 BLoC 模式将业务逻辑与视图隔离时(没有 bloc library),我们如何触发一次性 UI 调用(如显示快餐​​栏或警报对话框) 当一些异步逻辑在 BLoC 中完成时?例如,如果网络请求失败,我们希望显示一个对话框,通知用户无法获取某些数据。

我探索过的选项是:

  1. 在小部件的构建方法中使用 StreamBuilder 来侦听来自 bloc 的事件,并在 StreamBuilder 的构建方法中显示对话框。 这种方法的问题是 StreamBuilderbuild 方法实际上不会在这里 构建 任何东西。我们只是要求 Flutter 显示一个对话框。我们可以 return 一些不可见的 "fake" 小部件只是为了让 Flutter 开心,但这真的很 hacky。

  2. 使小部件有状态。在 initState 中,订阅来自 BLoC 的错误流,并在监听器中显示对话框。 这里我们保持 logic/view 的解耦,但据我所知,这个选项甚至不可行,因为我们没有显示对话框所需的 BuildContext

  3. 当小部件调用 BLoC 上的某些逻辑可能会导致应触发对话框的更改时,传入显示对话框的回调。在这里列出的选项中,我更喜欢这个。逻辑和视图仍然很独立,但感觉有点奇怪。 BLoC 模式中的数据流通常纯粹是 "widget -> BLoC via sinks or functions" 和 "BLoC -> widget via streams"。在某些逻辑执行后让小部件要求 BLoC 调用回调(将数据传回)是违反此模式的。

我一直在避免使用 bloc 库(为了保持简单,还有其他原因)。但是,它似乎确实以 BlocListener 的形式提供了解决此问题的方法。有没有一种简单的方法可以解决这个问题,同时保持 BLoC 的核心原则?

在进一步研究 StatefulWidget 之后,我发现我们实际上 可以访问 initState 中的 BuildContext context。因此,选项 #2 毕竟是可行的。它允许我们保持逻辑和视图分离,并保持数据仅使用流从 BLoC 流向小部件的模式。

重申解决方案:BLoC 可以公开数据流(例如错误代码),有状态小部件可以在 StateinitState 中订阅该数据流。当流发出一些数据时,侦听器可以使用 context 属性 来显示对话框。

我正在处理这个确切的问题。这是我的解决方案 - 它属于类别 #2:

class CurrencyStreamListener extends StatefulWidget {
  final Stream<CurrencyNotification> notificationStream;
  final Widget child;
  CurrencyStreamListener({this.notificationStream, this.child});

  @override
  _CurrencyStreamListenerState createState() => _CurrencyStreamListenerState();
}

class _CurrencyStreamListenerState extends State<CurrencyStreamListener> {
  @override
  void initState() {
    widget.notificationStream.listen((CurrencyNotification data) {
      if (data is NotificationOfIncrease) {
        Scaffold.of(context).showSnackBar(
          SnackBar(
            duration: Duration(seconds: 1),
            content: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text('Earned Currency'),
              ],
            ),
          ),
        );
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

这样我就可以将这个小部件嵌套在我可以访问 Bloc 流的任何地方,所以可能在某些提供者或消费者 class 下。

我所做的是:在我的 bloc 状态下,我有一个 属性 包含一个可以为 null 的 Snackbar 对象。

如果我需要显示一些消息,我将其设置为一个值,否则,我将其设置为空。

在我的 bloc 侦听器中,我检查它是否不为空并显示它。

为确保它只会被触发一次,我的 mapEventToState 中的第一行是生成一个新状态,其中消息 属性 为空。