Flutter Autocomplete在Overlay中触发无限重建循环,onSelected失效
问题解决:Overlay中Autocomplete引发父组件无限重建+回调失效
核心问题根源
- BlocProvider嵌套在build方法内:每次
MyLocationForm重建都会生成新的MyLocationsFormCubit实例,Cubit状态变化又触发组件重建,形成无限循环。 - Autocomplete状态管理混乱:异步的
optionsBuilder直接修改State变量但未调用setState,且Overlay上下文与组件上下文隔离,导致状态更新不生效,同时冷却逻辑存在竞态问题。 - 静态GlobalKey冲突:
_formKey设为static,多Overlay实例会导致Form状态混乱。 - BlocConsumer触发条件过宽:仅通过runtimeType判断状态变化,容易触发不必要的重建。
修复步骤
1. 将BlocProvider移到Overlay创建阶段
把Cubit的创建放在OverlayEntry的builder中,确保整个Overlay生命周期内Cubit实例唯一:
class MyLocationFormOverlay { MyLocationFormOverlay({MyLocation? myLocation, required BuildContext context}) { final OverlayState overlayState = Overlay.of(context)!; OverlayEntry? overlayEntry; overlayEntry = OverlayEntry(builder: (context) => BlocProvider( create: (context) => MyLocationsFormCubit()..initLocation(myLocation), child: DismissibleOverlay( overlayEntry: overlayEntry!, child: MyLocationForm(), ), )); overlayState.insert(overlayEntry); } }
同时修改MyLocationForm,移除内部的BlocProvider:
@override Widget build(BuildContext context) { return BasicContentContainer( child_ren: right( BlocConsumer<MyLocationsFormCubit, MyLocationsFormState>( listenWhen: (p,c) => p is MyLocationFormInitial && c is MyLocationFormAdding, listener: (context, state) { if(state is MyLocationFormAdding){ controller_name.text = state.location.name.getOrEmptyString(); controller_longitude.text = state.location.longitude.toString(); controller_latitude.text = state.location.latitude.toString(); } }, buildWhen: (p, c) => p.runtimeType != c.runtimeType, builder: (context, state) { return Form( key: _formKey, child: Column( children: [ Text(AppStrings.addLocation, style: AppTextStyles.stdLittleHeading,), FullWidthPaddingInput( labelText: AppStrings.name, controller: controller_name, hintText: AppStrings.name, onChanged: (value) => _formCubit(context).changeName(value), ), _buildAdressAutocomplete(context), _buildCoordFields(context), ], ), ); } ), ) ); }
2. 修复Autocomplete状态管理
用setState正确更新状态,优化冷却逻辑避免竞态:
Autocomplete<SearchInfoDetailed> _buildAdressAutocomplete(BuildContext context) { return Autocomplete<SearchInfoDetailed>( fieldViewBuilder: (context, fieldController, focusNode, onSubmitted) { return FullWidthPaddingInput( labelText: AppStrings.address, hintText: AppStrings.address, fieldFocusNode: focusNode, controller: fieldController, ); }, displayStringForOption: _displayStringForOption, optionsBuilder: (textEditingValue) async { if (textEditingValue.text.isEmpty) return const []; if (isCooldown) return _searchInfoOtions; timer?.cancel(); setState(() => isCooldown = true); timer = Timer(const Duration(milliseconds: cooldownDuration), () { if(mounted) setState(() => isCooldown = false); }); final results = await addressSuggestionDetailed(textEditingValue.text); if(mounted) setState(() => _searchInfoOtions = results); return results; }, onSelected: (selection) { final cubit = _formCubit(context); cubit.changeAddress(selection.addressDetailed.toString()); cubit.changeLatitude(selection.point?.latitude ?? 0); cubit.changeLongitude(selection.point?.longitude ?? 0); controller_longitude.text = selection.point?.longitude.toString() ?? ''; controller_latitude.text = selection.point?.latitude.toString() ?? ''; }, ); }
3. 修正GlobalKey和内存泄漏
- 将静态
_formKey改为实例变量:
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
- 在dispose中取消timer,避免内存泄漏:
@override void dispose() { timer?.cancel(); controller_name.dispose(); controller_longitude.dispose(); controller_latitude.dispose(); super.dispose(); }
4. 优化Bloc状态初始化
在Cubit中添加初始化方法,处理传入的myLocation:
class MyLocationsFormCubit extends Cubit<MyLocationsFormState> { MyLocationsFormCubit() : super(MyLocationFormInitial()); void initLocation(MyLocation? location) { if(location != null) { emit(MyLocationFormAdding(location: location)); } } // 其他业务方法... }
额外注意点
- Overlay上下文独立于页面上下文,所有状态操作需通过
mounted判断组件是否存活,避免空指针。 - Autocomplete的
optionsBuilder返回Future时,Flutter会自动处理异步结果,无需手动调用setState更新选项列表。
内容的提问来源于stack exchange,提问作者CKWDani
相关产品推荐
相关产品推荐

