更好的方式来处理StreamBuilder,同时我也想手动刷新流。

huangapple go评论52阅读模式
英文:

Better way to handle a StreamBuilder where I also want to manually refresh the stream

问题

以下是您提供的代码的翻译部分:

我有下面的这段代码,我认为它几乎可以工作,但还不够完善。基本上,我正在尝试使用StreamBuilder来监视数据库流。这个表格可以通过我的应用程序之外的任何方式进行更新,因此需要流。用户可以选择更改查询的时间范围以使用不同的DateTimeRange,我希望在这种情况下流也能更新。我可以使用setState并更新整个小部件来很容易地使事情正常工作,但这个小部件显示了来自google_maps_flutter包的地图,每次看到它更新都不是最好的体验。

这是我目前的尝试:我遇到了一些错误,比如未处理的异常:Bad state: Cannot add new events while doing an addStream,试图更新StreamBuilder的基础流以使用新的数据库查询

我最终得到了一个工作版本,但它非常丑陋,有两个流,一个用于检测刷新事件,另一个用于检测数据库更改。这两个流馈入到另一个与StreamBuilder连接的流中。这个流的问题是每次刷新或数据库更新时调用这些流数百次,这不是一种愉快的用户体验。

肯定有一种方法,我漏掉了这个方法。

TLDR; 我想要一个流到数据库,使用自定义查询,用户可以更改该查询并触发新的流发射,或者数据库更改可以触发流发射。我不想使用setState,因为那会刷新我使用的地图。

请注意,上述翻译只包括代码的主要内容,不包括对问题的回答或其他内容。如果您需要进一步的翻译或有其他问题,请随时提出。

英文:

I have this code below and it almost works I think, but it is not quite there. Essentially what I am trying to do it use a StreamBuilder to watch a database stream. This table may be updated by any means external to my app hence the stream. A user has the option to change the range of the query to use a different DateTimeRange though, and I want the stream to update in this case. I can get things working pretty easily using a setState and updating the whole widget but this widget shows a map from the google_maps_flutter package and it is not the prettiest thing to see watching it update each time.

This is my current attempt: I am having issues with errors such as Unhandled Exception: Bad state: Cannot add new events while doing an addStream trying to update the underlying stream of the StreamBuilder to use the new database query.

I did end up getting a working version that was very ugly by having two streams, one to detect refresh events, and one to detect the database changes. These two streams fed into another stream that would be connected to the StreamBuilder. This one had issues with the streams being called hundreds of times each refresh or database update which was not a pleasant UX.

There has to be some way that I am missing to do this.

TLDR; I want to have a stream to a database using a custom query where a user can change that query and also trigger a new stream emit, or the database changes can trigger a stream emit.I do not want to use setState as that will refresh the map I use.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: FutureBuilder(
        future: _fetchPrevDateRangeSettings(),
        builder: (_, ss) {
          if (ss.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }

          _backOfficeStreamController = StreamController.broadcast(); // Create a broadcast StreamController

          // refresh will take the most up to date query parameters and refresh the stream
          _refreshStreamController.stream.listen((_) async {
            _backOfficeStreamController.close();
            _backOfficeStreamController.addStream(ref
                .read(claimsDatabaseProvider)
                .fetchMapDataForDateTimeRange2(
                _dateTimeRange, filterByInspectionDate));
          });

          // will listen for database changes
          _backOfficeStreamController.addStream(ref
              .watch(claimsDatabaseProvider)
              .fetchMapDataForDateTimeRange2(
                  _dateTimeRange, filterByInspectionDate));

          // listen to the stream, and update when the user refreshes, or db updates
          return StreamBuilder(
            stream: _backOfficeStreamController.stream,
            builder: (_, ss2) {
              if (ss2.connectionState == ConnectionState.waiting) {
                return Center(child: CircularProgressIndicator());
              }
              return Column(
                children: [
                  Expanded(
                    flex: 1,
                    child: _getMap(context, ss2.requireData),
                  ),
                  Expanded(
                    child: Column(
                      children: [
                        _getFilterWidget(ss2.requireData),
                        Divider(thickness: 1),
                        _getListWidget(ss2.requireData),
                      ],
                    ),
                  ),
                ],
              );
            },
          );
        },
      ),
    );
  }

答案1

得分: 0

我无法从以上任何评论或答案中获得可用的解决方案,但我确实通过使用以下示例中所示的StreamGroup使其正常工作:

我只需在刷新操作中删除旧流,并添加新流。我确信可以通过使用流控制器来替换流,但我无法弄清楚,而使用流组可以保持代码相当简洁,即使它只保存一个流。

void _refreshData() async {
  // 关闭并重新打开流以避免错误
  streamGroup.remove(backOfficeStream);
  backOfficeStream = ref
      .read(claimsDatabaseProvider)
      .fetchMapDataForDateTimeRange2(_dateTimeRange, filterByInspectionDate);
  streamGroup.add(backOfficeStream);
}
@override
void dispose() async {
  _saveSettings();
  await streamGroup.remove(backOfficeStream);
  await streamGroup.close();
  super.dispose();
}
@override
Widget build(BuildContext context) {
  return Scaffold(
    resizeToAvoidBottomInset: false,
    body: FutureBuilder(
      future: _fetchPrevDateRangeSettings(),
      builder: (_, ss) {
        if (ss.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        }

        // 在类中更新流引用
        backOfficeStream = ref
            .read(claimsDatabaseProvider)
            .fetchMapDataForDateTimeRange2(_dateTimeRange, filterByInspectionDate);
        // 添加到流组
        streamGroup.add(backOfficeStream);

        // 使用StreamController的流进行StreamBuilder
        return StreamBuilder(
          stream: streamGroup.stream,
          builder: (_, ss2) {
            if (ss2.connectionState == ConnectionState.waiting) {
              return Center(child: CircularProgressIndicator());
            }
            return Column(
              children: [
                Expanded(
                  flex: 1,
                  child: _getMap(context, ss2.requireData),
                ),
                Expanded(
                  child: Column(
                    children: [
                      _getFilterWidget(ss2.requireData),
                      Divider(thickness: 1),
                      _getListWidget(ss2.requireData),
                    ],
                  ),
                ),
              ],
            );
          },
        );
      },
    ),
  );
}

希望这对你有所帮助。

英文:

I was not able to get a working solution from any of the comments or answers above, but I did get it working with using a StreamGroup as shown below:

I simply remove the old stream, and add the new one on a refresh action. I am sure there was a way to replace the stream using a stream controller, but I could not figure it out and using the stream group works while keeping the code pretty concise, even if it only ever holds one stream.

  void _refreshData() async {
    // Close and reopen the stream to avoid errors
    streamGroup.remove(backOfficeStream);
    backOfficeStream = ref
        .read(claimsDatabaseProvider)
        .fetchMapDataForDateTimeRange2(_dateTimeRange, filterByInspectionDate);
    streamGroup.add(backOfficeStream);
  }
  @override
  void dispose() async {
    _saveSettings();
    await streamGroup.remove(backOfficeStream);
    await streamGroup.close();
    super.dispose();
  }
@override
Widget build(BuildContext context) {
  return Scaffold(
    resizeToAvoidBottomInset: false,
    body: FutureBuilder(
      future: _fetchPrevDateRangeSettings(),
      builder: (_, ss) {
        if (ss.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        }

          // update the stream reference in the class
          backOfficeStream = ref
              .read(claimsDatabaseProvider)
              .fetchMapDataForDateTimeRange2(
                  _dateTimeRange, filterByInspectionDate);
          // add to stream group
          streamGroup.add(backOfficeStream);

        // Use the StreamController's stream for the StreamBuilder
        return StreamBuilder(
          stream: streamGroup.stream,
          builder: (_, ss2) {
            if (ss2.connectionState == ConnectionState.waiting) {
              return Center(child: CircularProgressIndicator());
            }
            return Column(
              children: [
                Expanded(
                  flex: 1,
                  child: _getMap(context, ss2.requireData),
                ),
                Expanded(
                  child: Column(
                    children: [
                      _getFilterWidget(ss2.requireData),
                      Divider(thickness: 1),
                      _getListWidget(ss2.requireData),
                    ],
                  ),
                ),
              ],
            );
          },
        );
      },
    ),
  );
}

huangapple
  • 本文由 发表于 2023年5月29日 03:26:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/76353258.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定