StreamBuilder在处置并再次构建后停止重绘

Luc*_*mic 6 dart flutter

项目结构

\n

我有下一个小部件,它使用 StreamBuilder 来监听绘制列表。\n(我省略了不相关的细节)

\n
class CloseUsersFromStream extends StatelessWidget {\n  final Stream<List<User>> _stream;\n  const CloseUsersFromStream(this._stream);\n\n  @override\n  Widget build(BuildContext context) {\n    return StreamBuilder<List<User>>(\n      stream: _stream,\n      builder: _buildWidgetFromSnapshot,\n    );\n  }\n\n  Widget _buildWidgetFromSnapshot(_, AsyncSnapshot<List<User>> snapshot) {\n    if (_snapshotHasLoaded(snapshot)) {\n      return UsersButtonsList(snapshot.data!);\n    } else {\n      return const Center(child: CircularProgressIndicator());\n    }\n  }\n\n  bool _snapshotHasLoaded(AsyncSnapshot<List<User>> snapshot) {\n    return snapshot.hasData;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

及其父级:

\n
class CloseUsersScreen extends StatefulWidget {\n  final ICloseUsersService _closeUsersService;\n\n  const CloseUsersScreen(this._closeUsersService);\n\n  @override\n  State<CloseUsersScreen> createState() => _CloseUsersScreenState();\n}\n\nclass _CloseUsersScreenState extends State<CloseUsersScreen> {\n  late final Stream<List<User>> _closeUsersStream;\n\n  @override\n  void initState() {\n    _initializeStream(context);\n    super.initState();\n  }\n\n  @override\n  void dispose(){\n    _closeStream();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return SafeArea(\n      child: Column(\n        children: [\n          //...\n          CloseUsersFromStream(_closeUsersStream),\n        ],\n      ),\n    );\n  }\n\n  void _initializeStream(BuildContext context) {\n    _closeUsersStream = widget._closeUsersService.openCloseUsersSubscription(context);\n  }\n\n  void _closeStream(){\n    widget._closeUsersService.closeCloseUsersSubscription();\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

问题

\n

有时,FutureBuilder当流上发生新事件时,我的不会重建。

\n

使用调试器,我注意到流工作正常并且StreamBuilder接收数据也正确。但是,它没有按预期重建。

\n

当我说StreamBuilder正确接收数据时,我的意思是调试器在线上停止

\n
return UsersButtonsList(snapshot.data!);\n
Run Code Online (Sandbox Code Playgroud)\n

通过调试器我可以看到这snapshot.data是预期的值。然而,Widget 不会重绘。

\n

不幸的是,当问题出现时我还无法找出模式。我认为在特定条件下处理后很可能会发生这种情况StreamBuilder(即使很困难它也不会打印任何错误),但我不能 100% 确定。我仍在尝试识别它,当我识别时我会将其添加到帖子中。

\n

用我自己的“ StreamBuilder

\n

我尝试构建自己的StreamBuilder以避免使用 Flutter,因为我认为小部件可能是问题所在

\n
class CustomStreamBuilder<T> extends StatefulWidget {\n  final Stream<T> stream;\n  final Widget Function(BuildContext context, T? data) builder;\n\n  const CustomStreamBuilder({required this.stream, required this.builder});\n\n  @override\n  _CustomStreamBuilderState<T> createState() => _CustomStreamBuilderState<T>();\n}\n\nclass _CustomStreamBuilderState<T> extends State<CustomStreamBuilder<T>> {\n  late StreamSubscription<T> _subscription;\n  T? _data;\n\n  @override\n  void initState() {\n    super.initState();;\n    _subscription = widget.stream.listen((data) {\n      setState(() {\n        _data = data;\n      });\n    });\n  }\n\n  @override\n  void dispose() {\n    _subscription.cancel();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return widget.builder(context, _data);\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然而,即使如此,问题仍然存在Widget。Stream 仍然可以正常工作,并且根据调试器的说法,Widget 应该正在重绘。我的意思是,当流接收到事件时,将再次执行此行:

\n
  Widget _buildWidgetFromSnapshot(BuildContext context, List<User>? snapshot) {\n      if(snapshot == null) {\n        return const Center(child: CircularProgressIndicator());\n      }else{\n        return UsersButtonsList(snapshot,_qualityReducer, _messageService); //<-- This line\n      }\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

和这个:

\n
  @override\n  Widget build(BuildContext context) {\n    return widget.builder(context, _data); //<-- This line\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

而且,正如之前发生的那样,变量_data具有从事件中给出的正确值。

\n

我尝试过的其他解决方案

\n

按照 @Sayyid J 告诉我的,我尝试使用ValueListenableBuilder

\n
class MyStreamNotifier<T> extends ValueNotifier implements ValueListenable {\n \n  MyStreamNotifier({required Stream<T> stream}) : super(null){\n    notifyListeners();\n    _subscription = stream.asBroadcastStream().listen((event) {\n      //when there is event. just rebuild\n      notifyListeners();\n      _event = event;\n    });\n\n    _subscription.onError((err) {\n      //handle the error\n    });\n\n  }\n\n  late final StreamSubscription<T> _subscription;\n\n T? _event;\n\n  @override\n  void dispose() {\n    _subscription.cancel();\n    super.dispose();\n  }\n  \n\n  @override\n  get value => _event;\n\n}\n\n\n\n\nclass CloseUsersFromStream extends StatelessWidget {\n  final Stream<List<User>> _stream;\n\n  const CloseUsersFromStream(this._stream);\n\n  @override\n  Widget build(BuildContext context) {\n    return ValueListenableBuilder(\n      valueListenable: MyStreamNotifier(stream: _stream),\n      builder: (context, event, child) {\n        if (event != null){\n          return UsersButtonsList(event);\n        }else{\n          return const Center(child: CircularProgressIndicator());\n        }\n      });\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

调试器在线上停止notifyListeners()。然而,它并没有停止builder功能。

\n
\n

添加一个UniqueKeyStreamBuilder

\n
  @override\n  Widget build(BuildContext context) {\n    return StreamBuilder<List<User>>(\n      key: UniqueKey(),\n      stream: _stream,\n      builder: _buildWidgetFromSnapshot,\n    );\n  }\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我认为问题可能是StreamBuilder在初始化之前被处置,这是我尝试过的其他事情:

\n

延迟一秒钟的处理以使StreamBuilder初始化正确。问题仍然存在。

\n
  @override\n  void dispose() {\n    sleep(const Duration(seconds:1));\n    _closeStream();\n    super.dispose();\n  }\n
Run Code Online (Sandbox Code Playgroud)\n
\n

StreamBuilder构建时强制更新。问题仍然存在。

\n
class CloseUsersFromStream extends StatefulWidget {\n  final Stream<List<User>> _stream;\n  const CloseUsersFromStream(this._stream);\n\n  @override\n  State<CloseUsersFromStream> createState() => _CloseUsersFromStreamState();\n}\n\nclass _CloseUsersFromStreamState extends State<CloseUsersFromStream> {\n\n  @override\n  void initState() {\n    setState(() {});\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return StreamBuilder<List<User>>(\n      stream: widget._stream,\n      builder: _buildWidgetFromSnapshot,\n    );\n  }\n\n   //...\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

StreamBuilder在 Stream 初始化之前不初始化。(这没有多大意义,因为 Stream 初始化不是未来,但我还是尝试了)

\n
class _CloseUsersScreenState extends State<CloseUsersScreen> {\n  Stream<List<User>>? _closeUsersStream;\n\n  //...\n\n  @override\n  Widget build(BuildContext context) {\n    return SafeArea(\n      child: Column(\n        children: [\n          CloseUsersHeader(),\n          if(_closeUsersStream != null) // <- This line here\n              CloseUsersFromStream(_closeUsersStream!),\n        ],\n      ),\n    );\n  }\n  void _initializeStream(BuildContext context) {\n    setState(() {\n       _closeUsersStream = widget._closeUsersService.openCloseUsersSubscription(context);\n    });\n  }\n\n  //...\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

正如 @B0Andrew 在评论中指出的那样,我尝试FutureBuilder用一​​个简单的 来更改 \ 的子项Text,忽略问题所在。但是,问题仍然存在。

\n
  Widget _buildWidgetFromSnapshot(BuildContext context, List<User>? snapshot) {\n    if(snapshot == null) {\n      return const Center(child: CircularProgressIndicator());\n    }else{\n      // return UsersButtonsList(snapshot;\n      return Text("Event received");\n    }\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

截图

\n

这是一个显示问题的小视频。请注意,CircularProgressIndicator停留在屏幕中。

\n

显示错误的 GIF

\n

但是,正如您所看到的,调试器确实停止在正确的行上:

\n

调试器

\n

这是问题的另一个视图,显示了调试器和应用程序:

\n

显示问题的 GIF

\n

吉图布

\n

这是Github 链接,用于观看完整项目。

\n

我读过的其他来源

\n

我查阅了以下来源但未成功:

\n\n

另一个例子

\n

发布原始问题几天后,我发现这个问题也出现在另一个StreamBuilder项目中。我已经尝试找出导致此错误出现的模式,但我还没有做到。

\n

这是StreamBuilder状态小部件(再次省略详细信息):

\n
class _ChatRendererState extends State<ChatRenderer> {\n\n  @override\n  void dispose() {\n    widget._chatService.closeChatStream();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return FutureBuilder(\n        future:widget._chatService.getChat(widget._externalUser),\n        builder:_buildChatFromFutureSnapshot,\n    );\n  }\n\n  Widget _buildChatFromFutureSnapshot(BuildContext context, AsyncSnapshot snapshot){\n    if(snapshot.hasData){\n      return StreamBuilder<Chat>(\n        initialData: snapshot.data,\n        stream: widget._chatService.getChatStream(context),\n        builder: (context, snapshotStream) {\n          return MessagesListWithLazyLoading(snapshotStream.data!); // <- Debugger stops in this line. But, the Widget doesn\'t rebuild\n        },\n      );\n    }else{\n      //...\n    }\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

这是从中获取 Stream 的类(无论如何,正如我之前所说,Stream 可以正常工作)

\n
class ChatStreamService implements IChatStreamService{\n  StreamController<Chat>? _closeUsersStreamController;\n  late WebSocketSubscription _chatSubscription;\n\n  @override\n  Stream<Chat> getChatStream(BuildContext context){\n    _closeUsersStreamController = StreamController<Chat>(); \n    _initializeSubscription(context);\n    return _closeUsersStreamController!.stream;\n  }\n\n  @override\n  void closeChatStream(){\n    _chatSubscription.unsuscribe();\n  }\n\n  void _initializeSubscription(BuildContext context){\n    _chatSubscription = WebSocketSubscription.activate(\n      //...\n      callback: _onChatReceived\n    );\n  }\n\n  void _onChatReceived(String? frameBody){\n    final chatJSON = jsonDecode(frameBody!);\n    Chat chat = Chat.fromJSON(chatJSON);\n    _closeUsersStreamController!.add(chat);      \n  }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在您自己的 PC 上运行该项目

\n

我知道提供一个最小的片段来重现问题会非常有帮助。但是,我无法重现它。我所能做的就是提供运行该项目的说明。我已经“dockerized”后端以使其易于运行。幸运的是,该项目非常小,只需几个命令即可运行。

\n

这是后端Github

\n

并且可以在项目的根目录下使用此命令运行:

\n
docker compose up\n
Run Code Online (Sandbox Code Playgroud)\n

这是前端 Github

\n

运行它所需要做的唯一更改是将文件config.dart( lib/config/config.dart) 中的服务器 IP 更改为您自己的 IP。

\n
Map<String, Widget Function(BuildContext)> routes = {\n   //...\n};\n\nconst String serverURL = \'192.168.50.92:8080\'; <-- This line\nconst String initalRoute = \'splash\';\nconst String title = "close";\nThemeData themeData = ThemeData(\n    //...\n)\n
Run Code Online (Sandbox Code Playgroud)\n

就我而言,我运行命令

\n
ifconfig  | grep "inet 19"\n
Run Code Online (Sandbox Code Playgroud)\n

在终端上,我得到了我的IP。

\n

在整个项目上重现问题

\n

一旦项目运行,问题可能会以不同的方式出现。一个正在重现下一个序列:

\n
    \n
  1. 注册(非常简单。只有3个字段,不需要任何验证)
  2. \n
\n

在此输入图像描述\n在此输入图像描述\n在此输入图像描述\n在此输入图像描述

\n
    \n
  1. 转到第二个屏幕并单击“Cerrar sesi\xc3\xb3n”
  2. \n
\n

在此输入图像描述\n在此输入图像描述

\n
    \n
  1. 使用相同的凭据登录
  2. \n
\n

在此输入图像描述

\n
    \n
  1. 再次转到第二个屏幕
  2. \n
\n

在此输入图像描述

\n
    \n
  1. 返回主屏幕
  2. \n
\n

在此输入图像描述

\n

而现在,问题应该已经出现了。如果不行,请尝试再次复制它。有时需要一两次尝试。

\n

以下是视频中的完整序列:

\n

错误再现全序列

\n

Oza*_*ran 2

是什么立即让我想知道:您的 UsersButtonList 有一个 const 构造函数。

您可以尝试不再通过 const 构造函数来创建您的小部件吗?我需要阅读这一点,但 Flutter 实际上会回收 const 的小部件来优化性能。这是如何发生的,到底是如何发生的,我必须再看一遍。

也许这已经是解决方案了。我在这里发现了类似的问题:

为什么 `const` 修饰符可以避免 Flutter 中的 StreamBuilder 重建?

另请参阅此答案/sf/answers/3744694621/

“就 Flutter 而言,const 的真正好处并不是减少实例化。当 widget 的实例没有更改时,Flutter 有一种特殊的处理方式:它不会重建它们。”


归档时间:

查看次数:

372 次

最近记录:

2 年,4 月 前