使用GetX实现Flutter抽屉时触发GetX误用错误,求解决方案
GetX开发Flutter抽屉组件时「[Get] the improper use of a GetX has been detected」错误排查与解决
问题描述
我是GetX新手,在使用GetX开发Flutter抽屉组件时,iconMenu()和invisibleSubMenu()两个方法均出现「[Get] the improper use of a GetX has been detected」错误。
错误日志
The following message was thrown building Obx(has builder, dirty, state: _ObxState#b6fe5): [Get] the improper use of a GetX has been detected. You should only use GetX or Obx for the specific widget that will be updated. If you are seeing this error, you probably did not insert any observable variables into GetX/Obx or insert them outside the scope that GetX considers suitable for an update (example: GetX => HeavyWidget => variableObservable). If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. The relevant error-causing widget was: Obx
相关代码
Drawer类代码
class DrawerScreen extends StatelessWidget { final bodyCtrl = Get.find<BodyController>(); DrawerScreen({super.key}); @override Widget build(BuildContext context) { return Container( child: row(), ); } row() { return Row( children: [ iconMenu(), invisibleSubMenu(), ], ); } iconMenu() { return Container( color: Colors.grey[400], width: 100, child: ListView.builder( itemCount: drawerItemscdm.length, itemBuilder: (context, index) { return Obx( () => InkWell( onTap: () { bodyCtrl.selectMenuIndex(index); }, child: Container( height: 45, alignment: Alignment.center, child: Icon( drawerItemscdm[index].icon, color: Colors.white, ), ), ), ); }), ); } invisibleSubMenu() { return Container( width: 150, color: Colors.red, child: Obx( () => ListView.builder( itemCount: drawerItemscdm.length, itemBuilder: (context, index) { Menucdm menu = drawerItemscdm[index]; bodyCtrl.selected.value = bodyCtrl.selectedIndex.value == index; if (bodyCtrl.selected.value) { return subMenuWidget(menu.title, menu.submenu); } return invisibleWidget(); }), ), ); } Container subMenuWidget(String title, List<Map> submenus) { return Container( color: Colors.blueGrey, height: 200, child: ListView.builder( itemCount: submenus.length, itemBuilder: (context, index) { return Column( children: [ ...submenus.map((element) => ListTile( leading: Icon(element['icon']), title: Text(element['title']), )), ], ); }), ); } invisibleWidget() { return Container( height: 45, color: Colors.green, ); } }
MenuCdm类代码
class Menucdm { final IconData icon; final String title; final List<Map> submenu; Menucdm(this.icon, this.title, this.submenu); }
drawerItemscdm列表代码
List<Menucdm> drawerItemscdm = [ Menucdm(FontAwesomeIcons.book, 'A', []), Menucdm(FontAwesomeIcons.book, 'B', [ { 'icon': FontAwesomeIcons.shop, 'title': 'BA', }, { 'icon': FontAwesomeIcons.shop, 'title': 'BB', }, { 'icon': FontAwesomeIcons.shop, 'title': 'BC', }, { 'icon': FontAwesomeIcons.shop, 'title': 'BD', } ]), Menucdm(FontAwesomeIcons.book, 'C', [ { 'icon': FontAwesomeIcons.gear, 'title': 'CA', }, { 'icon': FontAwesomeIcons.gear, 'title': 'CB', }, { 'icon': FontAwesomeIcons.gear, 'title': 'CC', } ])]
Controller类代码
class BodyController extends GetxController { final selectedIndex = (-1).obs; final selected = false.obs; void selectMenuIndex(int index) { selectedIndex.value = index; } }
错误原因分析
iconMenu()中的Obx无意义:Obx包裹的Widget没有使用任何可观察变量(obs),GetX会判定这是错误用法——Obx的核心作用是监听obs变量变化并重建UI,没有监听对象时就会抛出该错误。invisibleSubMenu()中的错误用法:- 直接在UI构建方法中修改Controller的
selected变量,每次UI重建都会触发状态变更,违背MVVM的状态管理原则。 selected是selectedIndex的派生值,不需要单独定义为obs,直接通过selectedIndex计算即可。
- 直接在UI构建方法中修改Controller的
修复方案
1. 修改Controller,移除多余的selected变量
class BodyController extends GetxController { final selectedIndex = (-1).obs; void selectMenuIndex(int index) { selectedIndex.value = index; } }
2. 修复iconMenu()方法
如果需要高亮选中的菜单选项,保留Obx并添加对selectedIndex的监听;如果不需要高亮,直接移除Obx:
带高亮效果的版本
Widget iconMenu() { return Container( color: Colors.grey[400], width: 100, child: ListView.builder( itemCount: drawerItemscdm.length, itemBuilder: (context, index) { return Obx( () => InkWell( onTap: () { bodyCtrl.selectMenuIndex(index); }, child: Container( height: 45, alignment: Alignment.center, // 根据选中状态切换背景色 color: bodyCtrl.selectedIndex.value == index ? Colors.grey[600] : Colors.transparent, child: Icon( drawerItemscdm[index].icon, color: Colors.white, ), ), ), ); }, ), ); }
无高亮效果的版本(直接移除Obx)
Widget iconMenu() { return Container( color: Colors.grey[400], width: 100, child: ListView.builder( itemCount: drawerItemscdm.length, itemBuilder: (context, index) { return InkWell( onTap: () { bodyCtrl.selectMenuIndex(index); }, child: Container( height: 45, alignment: Alignment.center, child: Icon( drawerItemscdm[index].icon, color: Colors.white, ), ), ); }, ), ); }
3. 修复invisibleSubMenu()方法
移除对bodyCtrl.selected的赋值,直接通过selectedIndex计算选中状态,确保Obx正确监听selectedIndex的变化:
Widget invisibleSubMenu() { return Container( width: 150, color: Colors.red, child: Obx( () => ListView.builder( itemCount: drawerItemscdm.length, itemBuilder: (context, index) { Menucdm menu = drawerItemscdm[index]; // 直接计算当前项是否选中 final isSelected = bodyCtrl.selectedIndex.value == index; if (isSelected) { return subMenuWidget(menu.title, menu.submenu); } return invisibleWidget(); }, ), ), ); }
4. 修复subMenuWidget()方法的重复渲染问题
原代码中ListView.builder的itemCount是submenus.length,但内部又遍历了所有submenus,导致重复创建ListTile,修改为按索引获取单个项:
Container subMenuWidget(String title, List<Map> submenus) { return Container( color: Colors.blueGrey, height: 200, child: ListView.builder( itemCount: submenus.length, itemBuilder: (context, index) { final submenuItem = submenus[index]; return ListTile( leading: Icon(submenuItem['icon']), title: Text(submenuItem['title']), ); }, ), ); }
内容的提问来源于stack exchange,提问作者Hamou Ouyaba
相关产品推荐
相关产品推荐

