Flutter中‘Tried to paint a RenderObject reentrantly’错误排查与修复
问题:Flutter Bloc状态管理下拉框出现渲染递归错误
尝试通过API获取国家名称,使用Bloc库进行状态管理并在自定义下拉框组件中展示,运行后触发渲染递归错误。
相关代码
1. 自定义下拉框组件 select_input_field.dart
import 'package:flutter/material.dart'; class SelectInputField extends StatelessWidget { final String label; final List<String> options; final TextEditingController controller; final EdgeInsets padding; const SelectInputField(this.label, this.options, { Key? key, required this.controller, this.padding = const EdgeInsets.only(top: 8, bottom: 8, left: 30, right: 30)}) : super(key: key); @override Widget build(BuildContext context) { return Padding( padding: padding, child: DropdownButtonFormField( decoration: InputDecoration( labelText: label, labelStyle: Theme.of(context).textTheme.bodyMedium, border: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).primaryColorDark, width: 1, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).primaryColorDark, width: 1, ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).colorScheme.secondary, width: 1, ), ), fillColor: Theme.of(context).primaryColorLight), style: Theme.of(context).textTheme.bodyMedium, items: options.map((String value) { print(value); return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), onChanged: (String? value) { controller.text = value ?? ''; }, ), ); } }
2. 下拉框构建方法
Widget _countriesDropDown(BuildContext context, TextEditingController controller){ return BlocProvider( create: (context) => _signupBloc, child: BlocBuilder<SignupBloc,SignupState>( buildWhen: ((previous, current) => previous is! Loaded && current is Loaded), builder: (context, state){ if (state is Initial){ _signupBloc.add(LoadEvent()); } else if (state is Loaded){ countries = state.countries; return SelectInputField("Country", countries, controller: controller); } return CircularProgressIndicator(color: Theme.of(context).colorScheme.secondary,); }, ) ); }
3. Bloc实现类
class SignupBloc extends Bloc<SignupEvent, SignupState> { static const String countriesUrl = "https://api.first.org/data/v1/countries"; SignupBloc() : super(Initial()) { on<LoadEvent>((event, emit) async{ emit(Loading()); List<String> countries = []; try{ final response = await http.get(Uri.parse(countriesUrl)); if(response.statusCode == 200){ var responseJson = jsonDecode(response.body); responseJson['data'].values.forEach((element) { countries.add(element['country']); }); emit(Loaded(countries)); } else{ emit(Failed("Error in getting data")); } } catch(e){ print(e); emit(Failed("Failed to access internet. Try again later!")); } }); }
错误信息
======== Exception caught by rendering library ===================================================== The following assertion was thrown during paint(): Tried to paint a RenderObject reentrantly. The following RenderObject was already being painted when it was painted again: RenderPointerListener#c100e relayoutBoundary=up20 NEEDS-PAINT ... needs compositing ... parentData: <none> (can use size) ... constraints: BoxConstraints(0.0<=w<=351.4, 0.0<=h<=Infinity) ... size: Size(351.4, 64.0) ... behavior: opaque ... listeners: down, panZoomStart Since this typically indicates an infinite recursion, it is disallowed. The relevant error-causing widget was: DropdownButtonFormField<String> DropdownButtonFormField:file:///D:/Projects/sem5/iblock/lib/widgets/forms/select_input_field.dart:20:14 When the exception was thrown, this was the stack: #0 RenderObject._paintWithContext.<anonymous closure> (package:flutter/src/rendering/object.dart:2693:9) #1 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2706:6) #2 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13) #3 RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:144:15) #4 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7) #5 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:239:13) #6 RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:144:15) #7 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2796:7)
Flutter环境信息
[√] Flutter (Channel stable, 3.3.2, on Microsoft Windows [Version 10.0.22621.608], locale en-US) • Flutter version 3.3.2 on channel stable at C:\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision e3c29ec00c (3 weeks ago), 2022-09-14 08:46:55 -0500 • Engine revision a4ff2c53d8 • Dart version 2.18.1 • DevTools version 2.15.0 [√] Android toolchain - develop for Android devices (Android SDK version 30.0.3) • Android SDK at C:\Users\ISHAD\AppData\Local\Android\sdk • Platform android-32, build-tools 30.0.3 • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) • All Android licenses accepted. [√] Chrome - develop for the web • CHROME_EXECUTABLE = C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe [√] Visual Studio - develop for Windows (Visual Studio Build Tools 2019 16.11.18) • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools • Visual Studio Build Tools 2019 version 16.11.32802.440 • Windows 10 SDK version 10.0.19041.0 [√] Android Studio (version 2021.3) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866) [√] IntelliJ IDEA Ultimate Edition (version 2022.2) • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA 2022.1.2 • Flutter plugin version 70.2.5 • Dart plugin version 222.4167.21 [√] VS Code (version 1.71.2) • VS Code at C:\Users\ISHAD\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.48.0 [√] Connected device (3 available) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.22621.608] • Chrome (web) • chrome • web-javascript • unknown • Edge (web) • edge • web-javascript • Microsoft Edge 105.0.1343.53 ! Device DRGGAM5890805202 is not authorized. You might need to check your device for an authorization dialog. [√] HTTP Host Availability • All required HTTP hosts are available • No issues found!
问题分析与修复方案
核心问题
- BlocProvider滥用导致状态循环:在
_countriesDropDown方法中重复返回同一个_signupBloc实例,同时在BlocBuilder的builder回调中触发LoadEvent,结合buildWhen的过滤条件,引发组件反复重建和状态更新。 - builder中修改外部变量:
countries = state.countries会触发组件重新build,加剧循环。 - 事件触发时机错误:在builder中触发事件,每次组件build到
Initial状态都会重复发起请求,导致状态频繁变化。
修复步骤
1. 调整Bloc初始化与事件触发时机
在Bloc构造函数中自动触发加载事件,避免在builder中手动触发:
class SignupBloc extends Bloc<SignupEvent, SignupState> { static const String countriesUrl = "https://api.first.org/data/v1/countries"; SignupBloc() : super(Initial()) { // 初始化时自动加载数据 add(LoadEvent()); on<LoadEvent>((event, emit) async{ emit(Loading()); List<String> countries = []; try{ final response = await http.get(Uri.parse(countriesUrl)); if(response.statusCode == 200){ var responseJson = jsonDecode(response.body); responseJson['data'].values.forEach((element) { countries.add(element['country']); }); emit(Loaded(countries)); } else{ emit(Failed("Error in getting data")); } } catch(e){ print(e); emit(Failed("Failed to access internet. Try again later!")); } }); }
2. 重构下拉框构建方法
移除内部的BlocProvider(将BlocProvider提升到父组件),同时去掉外部变量修改和不必要的buildWhen:
Widget _countriesDropDown(BuildContext context, TextEditingController controller){ return BlocBuilder<SignupBloc,SignupState>( builder: (context, state){ if (state is Initial || state is Loading) { return CircularProgressIndicator(color: Theme.of(context).colorScheme.secondary); } else if (state is Loaded) { // 直接使用state中的数据,不修改外部变量 return SelectInputField("Country", state.countries, controller: controller); } else if (state is Failed) { return Text((state as Failed).message); } return SizedBox.shrink(); }, ); }
3. 在父组件中提供Bloc实例
确保在页面层级提供Bloc,避免重复创建:
@override Widget build(BuildContext context) { return BlocProvider( create: (context) => SignupBloc(), child: Scaffold( body: // 页面内容,调用_countriesDropDown方法 ), ); }
4. 优化自定义下拉框组件
移除options.map中的print(value),避免大数据量下的性能问题。
内容的提问来源于stack exchange,提问作者Mohamed Ishad
相关产品推荐
相关产品推荐

