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

Flutter Autocomplete在Overlay中触发无限重建循环,onSelected失效

问题解决:Overlay中Autocomplete引发父组件无限重建+回调失效

核心问题根源

  1. BlocProvider嵌套在build方法内:每次MyLocationForm重建都会生成新的MyLocationsFormCubit实例,Cubit状态变化又触发组件重建,形成无限循环。
  2. Autocomplete状态管理混乱:异步的optionsBuilder直接修改State变量但未调用setState,且Overlay上下文与组件上下文隔离,导致状态更新不生效,同时冷却逻辑存在竞态问题。
  3. 静态GlobalKey冲突:_formKey设为static,多Overlay实例会导致Form状态混乱。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:33:21