如何在Flutter的ListView.builder中集成shared_preferences实现本地持久化存储
问题根源
- 存储逻辑错误:仅将待办文本拼接为无格式的长字符串存储,既没有保存待办的勾选状态,也没有做结构化拆分,应用重启后无法恢复为可用的列表数据
- 初始化逻辑缺失:启动时没有将本地存储的数据同步到内存的
items列表,所以原有基于items渲染的写法重启后数据全部丢失 - 渲染逻辑错误:修改后的写法是直接从完整拼接字符串中按字符索引取值,自然会出现展示异常
修复后可直接运行的完整代码
import 'dart:convert'; import "package:shared_preferences/shared_preferences.dart"; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("TodoList")), body: Container( child: Column( children: const [ Expanded(child: TodoListWidget()), ], ), ), ), ); } } // 待办数据模型,用于结构化存储 class Todo { String todoText; bool todoToggle; Todo({required this.todoText, required this.todoToggle}); Map<String, dynamic> toJson() => { "todoText": todoText, "todoToggle": todoToggle, }; static Todo fromJson(Map<String, dynamic> json) => Todo( todoText: json["todoText"], todoToggle: json["todoToggle"], ); } class TodoListWidget extends StatefulWidget { const TodoListWidget({Key? key}) : super(key: key); @override _TodoListWidgetState createState() => _TodoListWidgetState(); } class _TodoListWidgetState extends State<TodoListWidget> { final TextEditingController textInputController = TextEditingController(); List<Todo> items = []; late SharedPreferences prefs; final String storageKey = "todo_list"; @override void initState() { super.initState(); _initStorage(); } // 初始化本地存储,恢复历史数据 Future<void> _initStorage() async { prefs = await SharedPreferences.getInstance(); String? savedData = prefs.getString(storageKey); if (savedData != null && savedData.isNotEmpty) { List<dynamic> jsonList = jsonDecode(savedData); setState(() { items = jsonList.map((e) => Todo.fromJson(e)).toList(); }); } } // 同步数据到本地存储 Future<void> _syncToLocal() async { List<Map<String, dynamic>> jsonList = items.map((e) => e.toJson()).toList(); await prefs.setString(storageKey, jsonEncode(jsonList)); } @override Widget build(BuildContext context) { return Scaffold( body: Container( child: Column( children: [ items.isEmpty ? const Expanded(child: Center(child: Text("暂无待办事项"))) : Expanded( child: ListView.builder( itemCount: items.length, itemBuilder: (BuildContext context, int index) { return Card( child: Container( padding: const EdgeInsets.all(10.0), child: CheckboxListTile( title: _strikeThrough( todoText: items[index].todoText, todoToggle: items[index].todoToggle, ), value: items[index].todoToggle, onChanged: (bool? value) { setState(() { items[index].todoToggle = value!; }); _syncToLocal(); }, dense: true, activeColor: Colors.green, ), ), ); }, ), ), Center( child: FloatingActionButton( child: const Icon(Icons.add), onPressed: () => _displayAlert(context), ), ), ], ), ), ); } void _displayAlert(BuildContext context) { Widget contentDialog = AlertDialog( title: const Text("Add Task"), content: TextField(controller: textInputController), actions: [ TextButton( onPressed: () { Navigator.pop(context); textInputController.clear(); }, child: const Text("Cancel"), ), TextButton( onPressed: () { if (textInputController.text.trim().isEmpty) return; setState(() { items.add(Todo( todoText: textInputController.text.trim(), todoToggle: false, )); }); _syncToLocal(); Navigator.pop(context); textInputController.clear(); }, child: const Text("Add"), ) ], ); showDialog( context: context, builder: (BuildContext context) => contentDialog, ); } } class _strikeThrough extends StatelessWidget { final bool todoToggle; final String todoText; const _strikeThrough({required this.todoToggle, required this.todoText}); Widget _strikeWidget() { if (!todoToggle) { return Text( todoText, style: const TextStyle(fontSize: 20.0), ); } else { return Text( todoText, style: const TextStyle( fontSize: 22.0, decoration: TextDecoration.lineThrough, color: Colors.redAccent, fontStyle: FontStyle.italic, ), ); } } @override Widget build(BuildContext context) { return _strikeWidget(); } }
核心修改说明
- 新增了结构化的Todo数据模型,支持Json序列化/反序列化,可同时保存待办文本和勾选状态
- 初始化逻辑自动读取本地存储的历史数据,恢复到内存列表,重启应用数据不会丢失
- 每次新增待办、切换勾选状态都会自动同步数据到本地存储,保证数据一致性
- 保留了原有的渲染逻辑,展示效果和你之前正常运行的版本完全一致
- 移除了不必要的FutureBuilder嵌套和无用依赖,代码运行效率更高
内容的提问来源于stack exchange,提问作者Ewan1276243123
相关产品推荐
相关产品推荐

