Flutter+Riverpod+WebSocket报错:构建Widget树时修改Provider
解决Riverpod中"修改Provider时Widget树正在构建"的错误
问题分析
你遇到的错误Tried to modify a provider while the widget tree was building,核心原因是在Widget的构建流程中直接修改了StateProvider的状态:
- 在
HomeScreen的build方法里,messages.when(data: ...)回调属于Widget构建的一部分,你在这里调用updateStateFromMessage修改多个StateProvider的状态。 - 修改Provider状态会触发Widget重建,此时Widget还在构建中,就会引发冲突异常。
另外代码还有两处可优化点:
webSocketProvider用StateProvider不合理,因为WebSocket通道创建后不需要修改状态,用普通Provider更合适。- 消息解析时先编码再判断字符串包含的逻辑冗余,直接判断JSON对象的键更高效准确。
解决方案
核心修复思路
把消息处理和状态更新逻辑从UI层(Widget的build方法)移到StreamProvider内部,让Provider自己处理数据流并更新状态,避免在构建流程中修改状态。
修改步骤
调整WebSocket Provider类型
将webSocketProvider从StateProvider改为Provider,因为通道不需要动态修改:final webSocketProvider = Provider<IOWebSocketChannel>((ref) { final channel = IOWebSocketChannel.connect(Uri.parse('ws://192.168.206.115:81')); ref.onDispose(() => channel.sink.close()); return channel; });在StreamProvider内部处理消息并更新状态
重构providerOfMessages,直接在Provider中解析WebSocket消息并更新状态,不再向UI层传递原始消息:final providerOfMessages = StreamProvider.autoDispose<void>((ref) async* { final channel = ref.watch(webSocketProvider); ref.onDispose(() => channel.sink.close()); await for (final value in channel.stream) { final message = value.toString(); print(message); try { final jsondat = json.decode(message) as Map<String, dynamic>; if (jsondat.containsKey("connected")) { ref.read(connectedStatusProvider.notifier).state = true; ref.read(cycleStatusProvider.notifier).state = jsondat['cycle'] ?? 0; ref.read(forceStatusProvider.notifier).state = jsondat['force'] ?? 0; } } catch (e) { print('消息解析失败: $e'); } yield; } });简化UI层的Stream监听
在HomeScreen中,messages.when只需要处理加载和错误状态,不需要再处理数据(因为状态已经在Provider中更新):messages.when( data: (_) => const SizedBox.shrink(), loading: () => const CircularProgressIndicator(), error: (_, __) => const Text('服务器未运行,请检查WebSocket连接!'), )优化Switch事件回调的Provider获取方式
在Switch的onChanged中,用ref.read获取WebSocket通道(事件回调不是构建流程,用read更高效):onChanged: (bool value) { ref.read(switchedProvider.notifier).state = value; final channel = ref.read(webSocketProvider); if (value == false) { sendDataOff(channel); } else { sendDataOn(channel); } },
修改后的完整代码
// ignore_for_file: avoid_print import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:convert'; import 'package:web_socket_channel/io.dart'; import 'package:blinking_text/blinking_text.dart'; final switchedProvider = StateProvider<bool>((ref) => false); final connectedStatusProvider = StateProvider<bool>((ref) => false); final cycleStatusProvider = StateProvider<int>((ref) => 0); final forceStatusProvider = StateProvider<int>((ref) => 0); // 改为普通Provider,因为不需要修改通道状态 final webSocketProvider = Provider<IOWebSocketChannel>((ref) { final channel = IOWebSocketChannel.connect(Uri.parse('ws://192.168.206.115:81')); ref.onDispose(() => channel.sink.close()); return channel; }); void sendDataOn(IOWebSocketChannel channel) { channel.sink.add("poweron"); } void sendDataOff(IOWebSocketChannel channel) { channel.sink.add("poweroff"); } // 重构StreamProvider,内部处理消息和状态更新 final providerOfMessages = StreamProvider.autoDispose<void>((ref) async* { final channel = ref.watch(webSocketProvider); ref.onDispose(() => channel.sink.close()); await for (final value in channel.stream) { final message = value.toString(); print(message); try { final jsondat = json.decode(message) as Map<String, dynamic>; if (jsondat.containsKey("connected")) { ref.read(connectedStatusProvider.notifier).state = true; ref.read(cycleStatusProvider.notifier).state = jsondat['cycle'] ?? 0; ref.read(forceStatusProvider.notifier).state = jsondat['force'] ?? 0; } } catch (e) { print('消息解析失败: $e'); } yield; } }); void main() { runApp( const ProviderScope( child: MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( brightness: Brightness.light, ), darkTheme: ThemeData( brightness: Brightness.dark, ), themeMode: ThemeMode.dark, home: const HomeScreen(), ); } } class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final connected = ref.watch(connectedStatusProvider); final switched = ref.watch(switchedProvider); final messages = ref.watch(providerOfMessages); final istcycles = ref.watch(cycleStatusProvider); final istforcesensor = ref.watch(forceStatusProvider); return Scaffold( appBar: AppBar( title: const Text('WebSocket & Riverpod'), ), body: SafeArea( child: Center( child: Column( children: [ const Text('ESP32'), Container( child: connected ? Text( "CONNECTED", style: TextStyle( fontSize: 25.0, letterSpacing: 2.0, color: Colors.green[400], ), ) : const BlinkText( "DISCONNECTED", duration: Duration(milliseconds: 250), beginColor: Colors.red, endColor: Colors.white, style: TextStyle( fontSize: 25.0, letterSpacing: 2.0, color: Colors.red, ), ), ), Text( 'Cycles: ${istcycles.toString()}', style: const TextStyle(fontSize: 30), ), Text( 'Force: ${istforcesensor.toString()}', style: const TextStyle(fontSize: 30), ), Switch( value: switched, activeColor: connected && switched ? Colors.green[400] : Colors.red, inactiveThumbColor: connected && switched ? Colors.grey.shade400 : Colors.red, inactiveTrackColor: connected && switched ? Colors.grey.shade600 : Colors.red[200], onChanged: (bool value) { ref.read(switchedProvider.notifier).state = value; final channel = ref.read(webSocketProvider); if (value == false) { sendDataOff(channel); } else { sendDataOn(channel); } }, ), messages.when( data: (_) => const SizedBox.shrink(), loading: () => const CircularProgressIndicator(), error: (_, __) => const Text('服务器未运行,请检查WebSocket连接!'), ) ], ), ), ), ); } }
内容的提问来源于stack exchange,提问作者Carlos Costa
相关产品推荐
相关产品推荐

