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

Flutter中FutureBuilder嵌套GroupedListView出现类型转换错误求助

问题排查:GroupedListView加载SharedPreferences数据报错

在Flutter应用中使用GroupedListView按指定字段分组显示书签时,静态列表显示正常,但通过FutureBuilder从SharedPreferences加载动态数据时,出现错误:type 'string' is not a subtype of type 'int' of 'index'。

相关代码

class _BookmarkScreenState extends State<BookmarkScreen> {
  List bookmarks =[];


  @override
  void initState() {
    super.initState();
     _loadData();

    setState(() {});
  }

  Future<List<String>> _loadData() async {
    SharedPreferences pref = await SharedPreferences.getInstance();
    List<String> lb = await pref.getStringList(('bookmarks_key'));
    print(lb);
    lb.forEach((element) {
      bookmarks.add(element);
    });

    print(bookmarks);
    return bookmarks;
  }

 @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("Bookmark Screen")),
        body: Container(
            child: FutureBuilder(
    future: _loadData(),
    builder: (context, AsyncSnapshot snapshot) {
    if (!snapshot.hasData) {
    return Center(child: CircularProgressIndicator());
    } else {
                return GroupedListView<dynamic, String>(  // 报错行
                elements: bookmarks,
                groupBy: (element) => element['surah_name'],
                groupComparator: (value1, value2) => value2.compareTo(value1),
                itemComparator: (item1, item2) =>
                    item1['verse_no'].compareTo(item2['verse_no']),
                order: GroupedListOrder.DESC,
                useStickyGroupSeparators: true,
                groupSeparatorBuilder: (String value) => Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text(
                      value,
                      textAlign: TextAlign.center,
                      style:
                          TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                    )),
                itemBuilder: ((c, element) {
                  return Card(
                    elevation: 8.0,
                    margin: new EdgeInsets.symmetric(
                        horizontal: 10.0, vertical: 6.0),
                    child: Container(
                      child: ListTile(
                        contentPadding: EdgeInsets.symmetric(
                            horizontal: 20.0, vertical: 10.0),
                        title: Text(element['verse_no'].toString()),
                      ),
                    ),
                  );
                }));}})));
  }
}

静态正常示例

List bookmarks = [{"surah_no":2,"verse_no":2,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":2,"verse_no":1,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":2,"verse_no":2,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":2,"verse_no":3,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":4,"verse_no":1,"surah_name":"An-Nisa'","favorite":true}, {"surah_no":8,"verse_no":1,"surah_name":"Al-Anfal","favorite":true},];

问题原因

  1. 数据类型不匹配:SharedPreferences的getStringList返回List<String>,你直接将字符串元素添加到bookmarks列表,导致列表元素是字符串而非静态列表中的Map对象。当GroupedListView尝试用element['surah_name']访问时,会把字符串当作数组,用字符串'surah_name'作为索引(要求是int类型),因此抛出类型转换错误。
  2. 无效状态更新:initState中调用异步的_loadData后立即执行setState,此时数据尚未加载完成,属于无效操作。
  3. 重复加载数据:build方法每次执行都会调用_loadData(),导致SharedPreferences被重复读取,可能引发数据混乱。

修复步骤

1. 序列化/反序列化数据

存储书签时需将Map转为JSON字符串,读取时再解析回Map。修改_loadData方法:

Future<List<Map<String, dynamic>>> _loadData() async {
  SharedPreferences pref = await SharedPreferences.getInstance();
  List<String>? lb = pref.getStringList('bookmarks_key');
  if (lb == null) return [];
  
  // 将每个JSON字符串解析为Map
  List<Map<String, dynamic>> loadedBookmarks = lb.map((jsonStr) {
    return jsonDecode(jsonStr) as Map<String, dynamic>;
  }).toList();
  
  setState(() {
    bookmarks = loadedBookmarks;
  });
  return loadedBookmarks;
}

同时确保存储书签时的处理逻辑(示例存储代码):

Future<void> saveBookmark(Map<String, dynamic> bookmark) async {
  SharedPreferences pref = await SharedPreferences.getInstance();
  List<String> existing = pref.getStringList('bookmarks_key') ?? [];
  existing.add(jsonEncode(bookmark));
  await pref.setStringList('bookmarks_key', existing);
}

2. 修正FutureBuilder与状态管理

  • 调整_loadData返回类型为List<Map<String, dynamic>>,匹配实际数据类型。
  • 直接使用FutureBuilder的snapshot.data作为GroupedListView的数据源,避免依赖全局列表:
// 修改FutureBuilder部分
FutureBuilder<List<Map<String, dynamic>>>(
  future: _loadData(),
  builder: (context, AsyncSnapshot<List<Map<String, dynamic>>> snapshot) {
    if (!snapshot.hasData) {
      return const Center(child: CircularProgressIndicator());
    } 
    final bookmarks = snapshot.data!;
    return GroupedListView<Map<String, dynamic>, String>(
      elements: bookmarks,
      groupBy: (element) => element['surah_name'] as String,
      groupComparator: (value1, value2) => value2.compareTo(value1),
      itemComparator: (item1, item2) => 
          (item1['verse_no'] as int).compareTo(item2['verse_no'] as int),
      order: GroupedListOrder.DESC,
      useStickyGroupSeparators: true,
      groupSeparatorBuilder: (String value) => Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(
          value,
          textAlign: TextAlign.center,
          style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
      ),
      itemBuilder: ((c, element) {
        return Card(
          elevation: 8.0,
          margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
          child: ListTile(
            contentPadding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
            title: Text(element['verse_no'].toString()),
          ),
        );
      }),
    );
  },
)

3. 优化initState

移除initState中多余的setState:

@override
void initState() {
  super.initState();
  _loadData();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 18:57:09