Flutter同一表单页对应双路由切换时formKey为null、GlobalKey重复报错如何解决
问题根因与解决方案
根因分析
- 你为
/inventory和/openingInventory两个路由配置了相同的页面组件InventoryBottomTabBar,GetX路由默认会复用相同类型的页面实例,导致页面内部StateCommonPage的GlobalKey没有被重新初始化,同一时间组件树中存在两个持有相同GlobalKey的组件实例,触发重复GlobalKey断言。 StateCommonPage为无状态组件,内部声明的formKey、TextEditingController等属性不会随着组件销毁自动回收,也不会在新页面实例创建时强制生成新的实例,进一步加剧了Key冲突的概率。- 路由切换用的
Get.offNamed会在同一帧内完成旧页面销毁和新页面挂载,此时旧页面的Key还未从组件树中移除,新页面的相同Key已经被注册,触发框架校验报错。
解决方案
方案1:合并路由配置(最推荐)
两个路由仅业务参数不同,无需拆分两个路由配置,直接使用同一个路由,跳转时携带业务类型参数即可,从根源避免多实例冲突:
- 修改路由配置,仅保留一个路由:
class Routes { static const INVENTORY = '/inventory'; static final routes = [ GetPage( name: INVENTORY, page: () => InventoryBottomTabBar(), binding: BindingsBuilder.put(() => InventoryTabController()), ) ]; }
- 跳转时携带业务类型参数:
// 跳转到普通库存页 Get.offNamed('/inventory', arguments: {'type': 'normal'}); // 跳到期初库存页 Get.offNamed('/inventory', arguments: {'type': 'opening'});
- 页面内通过
Get.arguments获取参数区分业务逻辑即可。
方案2:强制生成独立页面实例
如果必须保留两个路由配置,修改路由和页面代码确保每个实例的Key唯一:
- 给两个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, ) ]; }
- 将
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
相关产品推荐
相关产品推荐

