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

Flutter Web表单SharedPreferences多实例错误求助

问题分析

你遇到的核心问题有两个:

  1. SharedPreferences实例未正确初始化:你的_spInstance方法只在widget.number == 1时才获取SharedPreferences实例,第二个模块(number=2)直接跳过初始化,导致childModuleData为null,调用getString时抛出TypeError。
  2. 存储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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 12:32:44