Flutter StreamBuilder中更新Provider触发setState during build报错如何解决
问题原因
setState() or markNeedsBuild() called during build 错误本质是在Widget的build执行阶段,触发了会导致组件重绘的状态更新操作。StreamBuilder的
builder回调本身属于Flutter绘制流程的一部分,你在回调中直接调用UbicacionesProvider的setXXX方法,方法内部执行notifyListeners时会通知所有监听该Provider的组件重绘,此时当前组件的build流程还没执行完成,因此触发框架的断言警告。
解决方案
方案1:延后状态更新到当前帧绘制完成(最小改动方案)
用Flutter提供的帧回调机制,把Provider更新逻辑放到当前帧绘制结束后执行,不会打断当前的build流程。只需要把要调用的Provider更新方法包裹在WidgetsBinding.instance.addPostFrameCallback中即可,修改后的代码如下:
if(es_actual){ latitudData = snapshot.data.latitude; longitudData = snapshot.data.longitude; Coordinates misCoordenadas = Coordinates(latitudData, longitudData); getAddress(misCoordenadas); // 把Provider更新逻辑放到帧回调中执行 WidgetsBinding.instance.addPostFrameCallback((_) { ubicacionesProvider.setlatitudActual(latitudData); ubicacionesProvider.setlongitudActual(longitudData); ubicacionesProvider.setubicacionActual(_miDireccionActual); }); }
方案2:业务逻辑与UI渲染分离(推荐规范方案)
StreamBuilder的builder方法只应该负责返回对应的UI组件,不应该承载业务逻辑和状态更新操作。你可以将定位流的监听逻辑移到当前页面State的initState生命周期中处理,从根源上避免在build阶段更新状态:
@override void initState() { super.initState(); // 直接监听定位流,不在StreamBuilder中处理业务逻辑 locationService.locationStream.listen((userLocation) { bool es_actual = ubicacionesProvider.ubicacionActualSeleccionada; bool es_elegida = ubicacionesProvider.ubicacionElegidaSeleccionada; if(es_actual){ double latitudData = userLocation.latitude; double longitudData = userLocation.longitude; Coordinates misCoordenadas = Coordinates(latitudData, longitudData); getAddress(misCoordenadas); // 直接更新Provider,不会触发build阶段的断言 ubicacionesProvider.setlatitudActual(latitudData); ubicacionesProvider.setlongitudActual(longitudData); ubicacionesProvider.setubicacionActual(_miDireccionActual); } if(es_elegida){ _latitudElegida = ubicacionesProvider.latitudElegida; _longitudElegida = ubicacionesProvider.longitudElegida; _miDireccionActual = ubicacionesProvider.ubicacionElegida; } }); }
该方案还可以避免StreamBuilder的builder被频繁触发时导致的重复执行业务逻辑问题,性能更稳定。采用这个方案后,StreamBuilder仅需要根据Provider的状态渲染对应UI即可,无需在builder中处理任何状态更新逻辑。
注意:如果你在build方法中获取ubicacionesProvider实例,需要设置listen: false避免Provider更新时触发不必要的重绘,写法为Provider.of<UbicacionesProvider>(context, listen: false)
内容的提问来源于stack exchange,提问作者mvasco

