Flutter Web表单SharedPreferences多实例错误求助
问题分析
你遇到的核心问题有两个:
- SharedPreferences实例未正确初始化:你的
_spInstance方法只在widget.number == 1时才获取SharedPreferences实例,第二个模块(number=2)直接跳过初始化,导致childModuleData为null,调用getString时抛出TypeError。 - 存储key冲突:所有子模块都用同一个key(比如
'Child First Name'),即使实例问题解决,多个模块的数据也会互相覆盖,根本无法区分不同子模块的信息。
解决方案
我们需要从实例初始化和唯一key设计两方面修复,同时优化初始化逻辑避免递归问题:
1. 修复SharedPreferences实例获取
删除对widget.number == 1的判断,每个子模块都正常获取SharedPreferences实例(SharedPreferences本身是单例,多次调用getInstance()不会创建多个实例,只是获取同一个单例,所以不用担心多实例问题)。
2. 为每个子模块生成唯一存储key
给每个存储key加上模块编号的后缀,比如'Child First Name ${widget.number}',这样不同模块的数据就不会互相覆盖。
3. 优化初始化逻辑
去掉原来的递归调用(childModuleData.clear()后又调用_spInstance()会导致无限循环风险),改为直接初始化默认值。
修改后的ChildModule代码
class ChildModule extends StatefulWidget { final int number; final Function() callback; ChildModule(this.number, this.callback); @override _ChildModule createState() => _ChildModule(); } class _ChildModule extends State<ChildModule> { SharedPreferences? childModuleData; // 改为可空类型更安全 bool loading = true; bool isOpen = true; // 生成当前模块的唯一key String get _firstNameKey => 'Child First Name ${widget.number}'; @override void initState() { print('this module number is ${widget.number}'); _initSharedPreferences(); super.initState(); } Future<void> _initSharedPreferences() async { // 所有模块都获取SharedPreferences实例 childModuleData = await SharedPreferences.getInstance(); print('got instance for module ${widget.number}'); // 初始化默认值(如果不存在的话) final currentValue = childModuleData?.getString(_firstNameKey); if (currentValue == null) { await childModuleData?.setString(_firstNameKey, ''); } setState(() { loading = false; }); } @override Widget build(BuildContext context) { return loading ? Loading() : Column( children: <Widget>[ GestureDetector( onTap: () { setState(() { isOpen = !isOpen; }); }, child: Container( height: 40, padding: EdgeInsets.only(left: 10, right: 10), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(4.0)), color: Colors.blue[50], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( 'Child ${widget.number} Details', style: TextStyle(color: Colors.blue[300], fontSize: 16), ), Row( children: <Widget>[ isOpen ? Icon( Icons.arrow_drop_down, size: 30, ) : Transform.rotate( angle: math.pi / 2, child: Icon( Icons.arrow_drop_down, size: 30, ), ), widget.number > 1 ? IconButton( icon: Icon(Icons.clear), onPressed: () async { await FormFunctions().removeModule(widget.number, EnquiryForm.numberOfChildren); // 删除当前模块的存储数据 await childModuleData?.remove(_firstNameKey); widget.callback(); }) : Container(), ], ), ], ), ), ), AnimatedContainer( duration: Duration(milliseconds: 300), // 缩短动画时间更流畅 curve: Curves.fastOutSlowIn, padding: EdgeInsets.fromLTRB(10, 5, 10, 5), height: isOpen ? null : 0, decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]), borderRadius: BorderRadius.all(Radius.circular(4.0)), ), child: Column( children: <Widget>[ Row( children: <Widget>[ CustomTextField( label: 'Child ${widget.number} First Name', isRequired: true, initialValue: childModuleData?.getString(_firstNameKey) ?? '', onChanged: (value){ childModuleData?.setString(_firstNameKey, value); }, ), ], ), ], ), ), ], ); } }
额外优化建议
- 把
EnquiryForm.numberOfChildren从静态变量移到_EnquiryFormState的成员变量里,静态变量可能导致状态混乱,尤其是在多页面场景下。 - 考虑使用
FormBuilder的字段来管理表单状态,同时同步到SharedPreferences,而不是直接在输入框的onChanged里写存储逻辑,这样表单验证和数据同步更统一。比如给每个子模块的字段加唯一名称,比如'child_${widget.number}_first_name',然后在表单提交时批量保存到SharedPreferences。
内容的提问来源于stack exchange,提问作者JobsyNZ
相关产品推荐
相关产品推荐

