使用Flutter GetX更新Observable List时触发StackOverflow错误
解决GetX中更新Protocol列表触发StackOverflow错误的问题
问题场景
开发一个允许用户回答多个protocol(如睡眠时长、疼痛)的页面,从Rest API获取protocols列表,当用户完成某个protocol的回答后,尝试更新列表中对应项时触发StackOverflow错误。
相关代码
GetxController代码
@override onInit() async { await fetchProtocol(); tabController = TabController(length: protocols.length, vsync: this); loading.value = false; super.onInit(); } Future<void> fetchProtocol() async { try { protocols.value = await repository.fetchProtocol(protocolId!); } catch (e) { showErrorDialog( dimissible: false, onClicked: () => Get.offAllNamed(Routes.training, arguments: trainignId), message: "Parece que você não pode responder a este protocolo."); } } updateProtocol(Protocol newProtocol) { protocols.value = replaceProtocolById(protocols, newProtocol); }
View代码
Widget build(BuildContext context) { return WillPopScope( onWillPop: () async => true, child: Scaffold( bottomNavigationBar: const SizedBox.shrink(), appBar: PreferredSize( preferredSize: const Size.fromHeight(50), child: AppBar( shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: const Radius.circular(5).r, ), ), title: Text(controller.title ?? "", maxLines: 1, style: TextStyle( color: Colors.white, fontSize: 22.sp, fontWeight: FontWeight.normal)), centerTitle: true, automaticallyImplyLeading: false, leading: null), ), body: SafeArea( child: Container( width: Get.width, height: Get.height, alignment: Alignment.topCenter, child: SizedBox( width: 342.w, child: Obx(() { if (controller.loading.value) { return Loading(); } else { return TabBarView( controller: controller.tabController, children: List.generate(controller.protocols.length, (index) { if (controller.protocols[index].id == 4) { return PainProtocol( onFeel: controller.changeViewedProtocol, onNotFeel: controller.changeViewedProtocol, position: controller.position.value, show: controller.viewedProtocol.value, onConfirmPains: controller.updateProtocol, protocol: controller.protocols.value[index], ); } else { return Center( child: Text( controller.protocols.value[index].name!), ); } })); } }), )), ))); }
Protocol类代码
class Protocol { int? id; String? name; String? nickname; String? measure; int? order; Value? value; List<Indicator>? indicator; String? type; Protocol( {this.id, this.name, this.nickname, this.measure, this.order, this.value, this.indicator, this.type}); }
问题原因
核心问题出在updateProtocol方法:
- 直接将可观察列表
protocols传入replaceProtocolById,如果该方法内部操作原列表或未生成新列表实例,赋值给protocols.value时会触发Obx重建 - Obx重建时重新生成TabBarView的所有子widget,每个子widget都会读取
protocols.value,进而再次触发列表更新,形成无限循环,最终导致StackOverflow
解决方案
1. 实现正确的replaceProtocolById方法,生成新列表实例
确保方法返回全新的列表,不修改原可观察列表:
List<Protocol> replaceProtocolById(List<Protocol> protocols, Protocol newProtocol) { return protocols.map((protocol) { return protocol.id == newProtocol.id ? newProtocol : protocol; }).toList(); }
2. 修复updateProtocol方法,传入列表快照
调用replaceProtocolById时传入protocols.value(当前列表的快照),避免直接操作可观察对象:
void updateProtocol(Protocol newProtocol) { protocols.value = replaceProtocolById(protocols.value, newProtocol); }
3. 动态更新TabController(可选优化)
监听protocols变化,确保TabController长度与列表同步,避免重建时出现异常:
@override void onInit() async { await fetchProtocol(); tabController = TabController(length: protocols.length, vsync: this); // 监听列表变化,更新TabController protocols.listen((newProtocols) { if (tabController.length != newProtocols.length) { tabController.dispose(); tabController = TabController(length: newProtocols.length, vsync: this); } }); loading.value = false; super.onInit(); }
4. 确保Protocol实例不可变
更新Protocol时生成新实例,而非修改原有对象,让GetX正确检测到变化:
// 示例:创建更新后的Protocol实例 Protocol updatedProtocol = Protocol( id: existingProtocol.id, name: existingProtocol.name, value: updatedValue, // 替换为用户提交的新值 nickname: existingProtocol.nickname, measure: existingProtocol.measure, order: existingProtocol.order, indicator: existingProtocol.indicator, type: existingProtocol.type, );
内容的提问来源于stack exchange,提问作者maremaFNX
相关产品推荐
相关产品推荐

