使用 Bloc、RxDart 和 StreamBuilder、无状态 vs 有状态和处置的 Flutter 干净架构

发布于 2025-01-13 16:04:42 字数 2706 浏览 7 评论 0原文

我正在尝试实现一个干净的架构,不依赖于业务逻辑层中的框架。

以下示例是仅包含文本的屏幕。我在存储库中进行 API Rest 调用,并将响应添加到通过将更新 TextStreamBuilder 侦听的 BehaviorSubject。由于是 StatefulWidget,我使用 dispose 方法来关闭 BehaviorSubjectStreamController

该示例是简化的,没有错误/加载状态处理,没有依赖项注入,基类,处置接口等。

class Bloc {
  final UserReposiotry _userReposiotry;
  final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);

  Bloc(this._userReposiotry) {
    _getActiveUsersCount();
  }

  void _getActiveUsersCount() async {
    final response = await _userReposiotry.getActiveUsersCount();
    _activeUsersCount.add(response.data);
  }

  ValueStream<int> get activeUsersCount => _activeUsersCount.stream;

  void dispose() async {
    await _activeUsersCount.drain(0);
    _activeUsersCount.close();
  }
}

class StatefulScreen extends StatefulWidget {
  final Bloc bloc;

  const StatefulScreen({Key? key, required this.bloc}) : super(key: key);

  @override
  State<StatefulScreen> createState() => _StatefulScreenState();
}

class _StatefulScreenState extends State<StatefulScreen> {
  @override
  Widget build(BuildContext context) {
    final stream = widget.bloc.activeUsersCount;
    return StreamBuilder<int>(
      stream: stream,
      initialData: stream.value,
      builder: (context, snapshot) {
        return Text(snapshot.data.toString());
      }
    );
  }

  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

我对这种方法有以下疑问。

  1. StreamBuilder 自动取消 stream 订阅,但不会关闭 StreamController。我知道如果你正在读取文件就应该关闭它,但是在这种情况下,如果我不手动关闭它,一旦 StatefulScreen 不再位于导航堆栈中,它是否会被销毁,还是内存泄漏
  2. 我见过很多人使用 StatelessWidget 而不是 StatefulWidget 使用 StreamStreamBuilder 方法,如果是的话确实需要关闭 BehaviorSubject 这是一个问题,因为我们没有 dispose 方法,我找到了 WillPopScope 但它不会' t 在所有导航情况下都会触发并且并且更重要的是,像 WillPopScope 这样的方法会更高效,还是在一个内部有一个 StatefulWidget wrapper (BlocProvider) StatelessWidget 只是为了进行处理,而不是直接使用 StatefulWidget ,如果是这样,您能指出该实现的示例吗?
  3. 我目前正在为具有动画或控制器(地图、文本输入、网页浏览...)或需要关闭的流的小部件选择 StatefulWidget ,其余的 StatelessWidget,这是正确的还是我错过了什么?
  4. 关于 drain 方法,我使用它是因为在 API 休息调用正在进行时返回时遇到错误,我找到了 RxDart 团队的成员说实际上没有必要调用 drain 所以我也对此感到困惑......,错误:

从 addStream 添加项目时无法关闭主题

感谢您的宝贵时间。

I'm trying to implement a clean architecture with no dependency of the framework in the business' logic layers.

The following example is a Screen with only a Text. I make an API Rest call in the repository and add the response to a BehaviorSubject that is listened through a StreamBuilder that will update the Text. Since is an StatefulWidget I'm using the dispose method to close the BehaviorSubject's StreamController.

The example is simplified, no error/loading state handling, no dependency injection, base classes, dispose interfaces etc.

class Bloc {
  final UserReposiotry _userReposiotry;
  final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);

  Bloc(this._userReposiotry) {
    _getActiveUsersCount();
  }

  void _getActiveUsersCount() async {
    final response = await _userReposiotry.getActiveUsersCount();
    _activeUsersCount.add(response.data);
  }

  ValueStream<int> get activeUsersCount => _activeUsersCount.stream;

  void dispose() async {
    await _activeUsersCount.drain(0);
    _activeUsersCount.close();
  }
}

class StatefulScreen extends StatefulWidget {
  final Bloc bloc;

  const StatefulScreen({Key? key, required this.bloc}) : super(key: key);

  @override
  State<StatefulScreen> createState() => _StatefulScreenState();
}

class _StatefulScreenState extends State<StatefulScreen> {
  @override
  Widget build(BuildContext context) {
    final stream = widget.bloc.activeUsersCount;
    return StreamBuilder<int>(
      stream: stream,
      initialData: stream.value,
      builder: (context, snapshot) {
        return Text(snapshot.data.toString());
      }
    );
  }

  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

I have the following doubts regarding this approach.

  1. StreamBuilder cancels the stream subscription automatically, but it doesn't close the StreamController. I know that you should close it if you are reading a file, but in this case, if I don't manually close it, once the StatefulScreen is no longer in the navigation stack, could it be destroyed, or it would be a memory leak?
  2. I've seen a lot of people using StatelessWidget instead of StatefulWidget using Stream and StreamBuilder approach, if it is really needed to close the BehaviorSubject it is a problem since we don't have the dispose method, I found about the WillPopScope but it won't fire in all navigation cases and also and more important would it be more performant an approach like WillPopScope, or having an StatefulWidget wrapper (BlocProvider) inside an StatelessWidget just to do the dispose, than using an StatefulWidget directly, and if so could you point to an example of that implementation?
  3. I'm currently choosing StatefulWidget for widgets that have animations o controllers (map, text input, pageview...) or streams that I need to close, the rest StatelessWidget, is this correct or am I missing something?
  4. About the drain method, I'm using it because I've encountered an error navigating back while an API rest call was on progress, I found a member of the RxDart team saying it isn't really necessary to call drain so I'm confused about this too..., the error:

You cannot close the subject while items are being added from addStream

Thanks for your time.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文