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

如何修复Flutter SharedPreferences的「Bad State: No element」错误及选词持久化

问题描述

现有Flutter代码从Firebase获取词汇,支持用户选择/取消选择词汇(选中项变色),并将状态同步保存到Firebase。现在需要添加SharedPreferences实现状态持久化:用户选择词汇点击「next」后,关闭重启App能保留选中/未选中状态,且仅选中项着色。但当前使用SharedPreferences时出现**「Bad State: No element」**错误,需解决该问题并完成功能。

错误原因

  1. 空列表调用first方法:initState中直接执行dropdownValueselectedWord = selectedWord.first,但selectedWord初始是空列表,调用first会触发「No element」错误。
  2. SharedPreferences存储逻辑错误:
    • 使用相同的key(SelectedWordObject)保存选中和未选中词汇,导致数据覆盖。
    • 用setString存储列表数据,SharedPreferences无法直接存储列表,需用setStringList。
  3. 初始化顺序错误:先执行取列表first的操作,再读取SharedPreferences数据,导致初始状态错误。

修复方案与完整代码

以下是修复后的完整代码,解决了上述问题并实现状态持久化:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hexcolor/hexcolor.dart';
import 'package:shared_preferences/shared_preferences.dart';
// 请确保导入你的Config、SignInProvider和HomeScreen
import 'your_import_path.dart';

class uitry extends StatefulWidget {
  const uitry({Key? key}) : super(key: key);

  @override
  State<uitry> createState() => _uitryState();
}

class _uitryState extends State<uitry> {
  List<Words12> wordList = [];
  List<String> selectedWord = [];
  List<String> deSelectedWord = [];

  // 从Firebase获取词汇列表
  Future<List<Words12>> fetchRecords() async {
    var records = await FirebaseFirestore.instance
        .collection('12words')
        .where("categoryName", isEqualTo: "Objects12")
        .get();
    return mapRecords(records);
  }

  List<Words12> mapRecords(QuerySnapshot<Map<String, dynamic>> records) {
    return records.docs.map((data) => Words12.fromJson(data.data())).toList();
  }

  @override
  void initState() {
    super.initState();
    _loadSavedState(); // 优先加载本地保存的状态
  }

  // 从SharedPreferences加载保存的选中/未选中状态
  Future<void> _loadSavedState() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    // 读取选中列表,为空则初始化空列表
    List<String>? savedSelected = prefs.getStringList("selected_words_objects");
    // 读取未选中列表,为空则后续从Firebase数据初始化
    List<String>? savedDeSelected = prefs.getStringList("deselected_words_objects");

    setState(() {
      selectedWord = savedSelected ?? [];
      deSelectedWord = savedDeSelected ?? [];
    });

    // 如果未选中列表为空,首次加载时将所有Firebase词汇设为未选中(除了已选中的)
    if (savedDeSelected == null) {
      List<Words12> words = await fetchRecords();
      setState(() {
        deSelectedWord = words
            .map((word) => word.wordName)
            .where((name) => !selectedWord.contains(name))
            .toList();
      });
    }
  }

  // 保存选中/未选中状态到SharedPreferences
  Future<void> _saveState() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setStringList("selected_words_objects", selectedWord);
    await prefs.setStringList("deselected_words_objects", deSelectedWord);
  }

  @override
  Widget build(BuildContext context) {
    double height = MediaQuery.of(context).size.height;
    double width = MediaQuery.of(context).size.width;
    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          image: DecorationImage(
              image: AssetImage(Config.app_background4), fit: BoxFit.fill),
        ),
        child: SafeArea(
            child: Center(
                child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Expanded(
                    child: ListTile(
                      leading: GestureDetector(
                        child: const Icon(
                          Icons.arrow_back_ios_new_sharp,
                          color: Colors.black,
                          size: 24.0,
                        ),
                        onTap: () => Navigator.pop(context),
                      ),
                      title: const Padding(
                        padding: EdgeInsets.only(top: 32, right: 35),
                        child: Text(
                          "Under 18 months",
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: Colors.black,
                            fontSize: 18.00,
                            fontWeight: FontWeight.w700,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 0),
              Padding(
                padding: const EdgeInsets.only(top: 1, right: 0),
                child: Column(
                  children: [
                    Material(
                      color: HexColor('#E92F54').withOpacity(0.9),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(0).copyWith(
                          topLeft: const Radius.circular(28.0),
                          topRight: const Radius.circular(28.0),
                        ),
                      ),
                      child: SizedBox(
                        width: width * 0.94,
                        height: height * 0.062,
                        child: const Column(
                          children: [
                            SizedBox(height: 6.5),
                            Text('Understanding',
                                style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.w700,
                                    fontSize: 16.0)),
                            Text('Object',
                                style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.w700,
                                    fontSize: 15.0))
                          ],
                        ),
                      ),
                    ),
                    Material(
                      color: HexColor('#FFFBFB').withOpacity(0.7),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(2).copyWith(
                          bottomLeft: const Radius.circular(28.0),
                          bottomRight: const Radius.circular(28.0),
                        ),
                      ),
                      child: SizedBox(
                        width: width * 0.94,
                        height: height * 0.30,
                        child: Column(
                          children: [
                            SizedBox(
                              height: height * 0.18,
                              child: SingleChildScrollView(
                                child: Column(
                                  children: const [SizedBox(height: 10)],
                                ),
                              ),
                            ),
                            Expanded(
                              child: FutureBuilder<List<Words12>>(
                                  future: fetchRecords(),
                                  builder: (context, snapshot) {
                                    if (snapshot.hasError) {
                                      return Text('Error: ${snapshot.error}');
                                    } else if (!snapshot.hasData) {
                                      return const Center(child: CircularProgressIndicator());
                                    } else {
                                      wordList = snapshot.data!;

                                      // 如果未选中列表为空(首次加载),初始化未选中列表
                                      if (deSelectedWord.isEmpty && selectedWord.isEmpty) {
                                        deSelectedWord = wordList.map((word) => word.wordName).toList();
                                      }

                                      return SingleChildScrollView(
                                        child: Wrap(
                                          children: wordList.map((word) {
                                            bool isSelected = selectedWord.contains(word.wordName);
                                            return GestureDetector(
                                              onTap: () {
                                                setState(() {
                                                  if (!isSelected) {
                                                    if (selectedWord.length < 50) {
                                                      selectedWord.add(word.wordName);
                                                      deSelectedWord.remove(word.wordName);
                                                    }
                                                  } else {
                                                    selectedWord.remove(word.wordName);
                                                    deSelectedWord.add(word.wordName);
                                                  }
                                                });
                                              },
                                              child: Container(
                                                margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 4),
                                                child: Container(
                                                  padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12),
                                                  decoration: BoxDecoration(
                                                      color: isSelected ? HexColor('#3A97FF') : HexColor('#D9D9D9'),
                                                      borderRadius: BorderRadius.circular(18),
                                                      border: Border.all(
                                                          color: isSelected ? HexColor('#3A97FF') : HexColor('#D9D9D9'),
                                                          width: 1)),
                                                  child: Text(
                                                    word.wordName,
                                                    style: const TextStyle(
                                                        color: Colors.black,
                                                        fontSize: 14,
                                                        fontWeight: FontWeight.w700),
                                                  ),
                                                ),
                                              ),
                                            );
                                          }).toList(),
                                        ),
                                      );
                                    }
                                  }),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 25),
              Center(
                child: SizedBox(
                  width: 160.0,
                  height: 35.0,
                  child: ElevatedButton(
                    style: ButtonStyle(
                      shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                        RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(18.0),
                          side: const BorderSide(color: Colors.blueAccent),
                        ),
                      ),
                    ),
                    onPressed: displayMessage,
                    child: const Text("next"),
                  ),
                ),
              ),
            ],
          ),
        ))),
      ),
    );
  }

  void displayMessage() async {
    // 先保存状态到SharedPreferences
    await _saveState();

    // 同步到Firebase
    final sp = context.read<SignInProvider>();
    await FirebaseFirestore.instance.collection("objects").doc(sp.uid).set({
      "speackSE": selectedWord,
      "speackUN": deSelectedWord,
    });

    // 跳转到首页
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => const HomeScreen()),
    );
  }
}

// 请确保Words12类已正确实现
class Words12 {
  final String wordName;
  final String categoryName;

  Words12({required this.wordName, required this.categoryName});

  factory Words12.fromJson(Map<String, dynamic> json) {
    return Words12(
      wordName: json['wordName'],
      categoryName: json['categoryName'],
    );
  }
}

关键修复点说明

  1. 移除空列表first调用:删除initState中直接取selectedWord.first的代码,改为从SharedPreferences加载数据后初始化状态。
  2. SharedPreferences列表存储:使用setStringList和getStringList来保存和读取选中/未选中词汇列表,分别使用不同的key避免数据覆盖。
  3. 初始化逻辑调整:首次加载时,如果未保存过状态,将Firebase返回的所有词汇设为未选中;如果有保存的状态,则直接恢复。
  4. 状态保存时机:点击「next」按钮时,先保存状态到SharedPreferences,再同步到Firebase,确保本地持久化优先完成。
  5. UI状态更新:在加载本地数据和点击词汇时调用setState,确保UI实时更新选中状态和颜色。

内容的提问来源于stack exchange,提问作者Dasun Dola

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 02:01:11