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

Flutter同一表单页对应双路由切换时formKey为null、GlobalKey重复报错如何解决

问题根因与解决方案

根因分析

  • 你为/inventory和/openingInventory两个路由配置了相同的页面组件InventoryBottomTabBar,GetX路由默认会复用相同类型的页面实例,导致页面内部StateCommonPage的GlobalKey没有被重新初始化,同一时间组件树中存在两个持有相同GlobalKey的组件实例,触发重复GlobalKey断言。
  • StateCommonPage为无状态组件,内部声明的formKey、TextEditingController等属性不会随着组件销毁自动回收,也不会在新页面实例创建时强制生成新的实例,进一步加剧了Key冲突的概率。
  • 路由切换用的Get.offNamed会在同一帧内完成旧页面销毁和新页面挂载,此时旧页面的Key还未从组件树中移除,新页面的相同Key已经被注册,触发框架校验报错。

解决方案

方案1:合并路由配置(最推荐)

两个路由仅业务参数不同,无需拆分两个路由配置,直接使用同一个路由,跳转时携带业务类型参数即可,从根源避免多实例冲突:

  1. 修改路由配置,仅保留一个路由:
class Routes {
  static const INVENTORY = '/inventory';
  static final routes = [
    GetPage(
      name: INVENTORY,
      page: () => InventoryBottomTabBar(),
      binding: BindingsBuilder.put(() => InventoryTabController()),
    )
  ];
}
  1. 跳转时携带业务类型参数:
// 跳转到普通库存页
Get.offNamed('/inventory', arguments: {'type': 'normal'});
// 跳到期初库存页
Get.offNamed('/inventory', arguments: {'type': 'opening'});
  1. 页面内通过Get.arguments获取参数区分业务逻辑即可。

方案2:强制生成独立页面实例

如果必须保留两个路由配置,修改路由和页面代码确保每个实例的Key唯一:

  1. 给两个GetPage配置preventDuplicates: false,强制GetX不复用页面实例,同时给控制器加不同tag避免控制器复用:
class Routes {
  static const INVENTORY = '/inventory';
  static const OPENING_INVENTORY = '/openingInventory';
  static final routes = [
    GetPage(
      name: INVENTORY,
      page: () => InventoryBottomTabBar(),
      binding: BindingsBuilder.put(() => InventoryTabController(), tag: INVENTORY),
      preventDuplicates: false,
    ),
    GetPage(
      name: OPENING_INVENTORY,
      page: () => InventoryBottomTabBar(),
      binding: BindingsBuilder.put(() => InventoryTabController(), tag: OPENING_INVENTORY),
      preventDuplicates: false,
    )
  ];
}
  1. 将StateCommonPage改为有状态组件,把formKey、输入框控制器都放到State内部,页面销毁时自动回收资源,也确保每次实例化生成新的Key:
class StateCommonPage extends StatefulWidget {
  final States titleText;
  const StateCommonPage({super.key, required this.titleText});

  @override
  State<StateCommonPage> createState() => _StateCommonPageState();
}

class _StateCommonPageState extends State<StateCommonPage> {
  late final InventoryTabController _inventoryTabController;
  final VariationController _variationController = Get.find();
  final CommonWidget _commonWidget = CommonWidget();
  final ValidationMethods _validationMethods = ValidationMethods();
  final description = TextEditingController();
  final quantity = TextEditingController();
  // 用当前路由作为后缀保证Key唯一
  final GlobalKey<FormState> formKey = GlobalKey<FormState>(debugLabel: 'STATE_COMMON_${Get.currentRoute}');

  double? width;
  double? height;

  @override
  void initState() {
    super.initState();
    // 获取控制器时携带当前路由作为tag
    _inventoryTabController = Get.find(tag: Get.currentRoute);
  }

  @override
  void dispose() {
    // 销毁输入控制器避免内存泄漏
    description.dispose();
    quantity.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    width = Get.width * 0.95;
    height = Get.height;
    // 原有build逻辑保持不变即可
    return Scaffold(
      // 原有页面代码
    );
  }
}

内容的提问来源于stack exchange,提问作者MrVaishnav Kuldeep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 15:15:09