You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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还在构建中,就会引发冲突异常。

另外代码还有两处可优化点:

  1. webSocketProvider用StateProvider不合理,因为WebSocket通道创建后不需要修改状态,用普通Provider更合适。
  2. 消息解析时先编码再判断字符串包含的逻辑冗余,直接判断JSON对象的键更高效准确。

解决方案

核心修复思路

把消息处理和状态更新逻辑从UI层(Widget的build方法)移到StreamProvider内部,让Provider自己处理数据流并更新状态,避免在构建流程中修改状态。

修改步骤

  1. 调整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;
    });
    
  2. 在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;
      }
    });
    
  3. 简化UI层的Stream监听
    在HomeScreen中,messages.when只需要处理加载和错误状态,不需要再处理数据(因为状态已经在Provider中更新):

    messages.when(
      data: (_) => const SizedBox.shrink(),
      loading: () => const CircularProgressIndicator(),
      error: (_, __) => const Text('服务器未运行,请检查WebSocket连接!'),
    )
    
  4. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 13:50:43