我有下一个小部件,它使用 StreamBuilder 来监听绘制列表。\n(我省略了不相关的细节)
\nclass 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}\nRun Code Online (Sandbox Code Playgroud)\n及其父级:
\nclass 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}\nRun Code Online (Sandbox Code Playgroud)\n有时,FutureBuilder当流上发生新事件时,我的不会重建。
使用调试器,我注意到流工作正常并且StreamBuilder接收数据也正确。但是,它没有按预期重建。
当我说StreamBuilder正确接收数据时,我的意思是调试器在线上停止
return UsersButtonsList(snapshot.data!);\nRun Code Online (Sandbox Code Playgroud)\n通过调试器我可以看到这snapshot.data是预期的值。然而,Widget 不会重绘。
不幸的是,当问题出现时我还无法找出模式。我认为在特定条件下处理后很可能会发生这种情况StreamBuilder(即使很困难它也不会打印任何错误),但我不能 100% 确定。我仍在尝试识别它,当我识别时我会将其添加到帖子中。
StreamBuilder”我尝试构建自己的StreamBuilder以避免使用 Flutter,因为我认为小部件可能是问题所在
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}\nRun Code Online (Sandbox Code Playgroud)\n然而,即使如此,问题仍然存在Widget。Stream 仍然可以正常工作,并且根据调试器的说法,Widget 应该正在重绘。我的意思是,当流接收到事件时,将再次执行此行:
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 }\nRun Code Online (Sandbox Code Playgroud)\n和这个:
\n @override\n Widget build(BuildContext context) {\n return widget.builder(context, _data); //<-- This line\n }\nRun Code Online (Sandbox Code Playgroud)\n而且,正如之前发生的那样,变量_data具有从事件中给出的正确值。
按照 @Sayyid J 告诉我的,我尝试使用ValueListenableBuilder
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}\nRun Code Online (Sandbox Code Playgroud)\n调试器在线上停止notifyListeners()。然而,它并没有停止builder功能。
添加一个UniqueKey到StreamBuilder
@override\n Widget build(BuildContext context) {\n return StreamBuilder<List<User>>(\n key: UniqueKey(),\n stream: _stream,\n builder: _buildWidgetFromSnapshot,\n );\n }\nRun Code Online (Sandbox Code Playgroud)\n我认为问题可能是StreamBuilder在初始化之前被处置,这是我尝试过的其他事情:
延迟一秒钟的处理以使StreamBuilder初始化正确。问题仍然存在。
@override\n void dispose() {\n sleep(const Duration(seconds:1));\n _closeStream();\n super.dispose();\n }\nRun Code Online (Sandbox Code Playgroud)\nStreamBuilder构建时强制更新。问题仍然存在。
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}\nRun Code Online (Sandbox Code Playgroud)\nStreamBuilder在 Stream 初始化之前不初始化。(这没有多大意义,因为 Stream 初始化不是未来,但我还是尝试了)
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}\nRun Code Online (Sandbox Code Playgroud)\n正如 @B0Andrew 在评论中指出的那样,我尝试FutureBuilder用一个简单的 来更改 \ 的子项Text,忽略问题所在。但是,问题仍然存在。
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 }\nRun Code Online (Sandbox Code Playgroud)\n这是一个显示问题的小视频。请注意,CircularProgressIndicator停留在屏幕中。
但是,正如您所看到的,调试器确实停止在正确的行上:
\n\n这是问题的另一个视图,显示了调试器和应用程序:
\n\n这是Github 链接,用于观看完整项目。
\n我查阅了以下来源但未成功:
\n发布原始问题几天后,我发现这个问题也出现在另一个StreamBuilder项目中。我已经尝试找出导致此错误出现的模式,但我还没有做到。
这是StreamBuilder状态小部件(再次省略详细信息):
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 }\nRun Code Online (Sandbox Code Playgroud)\n这是从中获取 Stream 的类(无论如何,正如我之前所说,Stream 可以正常工作)
\nclass 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}\nRun Code Online (Sandbox Code Playgroud)\n我知道提供一个最小的片段来重现问题会非常有帮助。但是,我无法重现它。我所能做的就是提供运行该项目的说明。我已经“dockerized”后端以使其易于运行。幸运的是,该项目非常小,只需几个命令即可运行。
\n\n并且可以在项目的根目录下使用此命令运行:
\ndocker compose up\nRun Code Online (Sandbox Code Playgroud)\n\n运行它所需要做的唯一更改是将文件config.dart( lib/config/config.dart) 中的服务器 IP 更改为您自己的 IP。
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)\nRun Code Online (Sandbox Code Playgroud)\n就我而言,我运行命令
\nifconfig | grep "inet 19"\nRun Code Online (Sandbox Code Playgroud)\n在终端上,我得到了我的IP。
\n一旦项目运行,问题可能会以不同的方式出现。一个正在重现下一个序列:
\n而现在,问题应该已经出现了。如果不行,请尝试再次复制它。有时需要一两次尝试。
\n以下是视频中的完整序列:
\n\n是什么立即让我想知道:您的 UsersButtonList 有一个 const 构造函数。
您可以尝试不再通过 const 构造函数来创建您的小部件吗?我需要阅读这一点,但 Flutter 实际上会回收 const 的小部件来优化性能。这是如何发生的,到底是如何发生的,我必须再看一遍。
也许这已经是解决方案了。我在这里发现了类似的问题:
为什么 `const` 修饰符可以避免 Flutter 中的 StreamBuilder 重建?
另请参阅此答案/sf/answers/3744694621/
“就 Flutter 而言,const 的真正好处并不是减少实例化。当 widget 的实例没有更改时,Flutter 有一种特殊的处理方式:它不会重建它们。”
| 归档时间: |
|
| 查看次数: |
372 次 |
| 最近记录: |