Flutter如何使用SharedPreferences持久化存储List<String>待办列表
注意:shared_preferences 2.0及以上稳定版本原生支持
setStringList/getStringList方法,可直接读写List<String>类型数据,不需要额外做json序列化转换,建议使用前先升级到最新稳定版。
shared_preferences 原生就支持List<String>类型的直接读写,你只需要补全初始化读取、变更后写入的逻辑即可,同时可以同步优化现有代码的规范问题。
一、核心实现步骤
- 定义固定的存储key,避免硬编码拼写错误
- 在页面初始化生命周期
initState中异步读取本地已存储的待办列表,替换原有硬编码测试数据 - 抽离独立的列表保存方法,每次新增、删除、修改待办项后,同步把最新列表写入本地存储
- 补全资源释放、输入校验等边界逻辑
修改后的完整可运行代码
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tooodo/views/task_lists_containers.dart'; class TasksScreen extends StatefulWidget { const TasksScreen({Key? key}) : super(key: key); @override State<TasksScreen> createState() => _TasksScreenState(); } class _TasksScreenState extends State<TasksScreen> { // 本地存储key常量,避免硬编码出错 static const String _todoStorageKey = 'local_todo_list'; List<String> toDoItems = []; final TextEditingController _taskTextFieldController = TextEditingController(); @override void initState() { super.initState(); // 页面初始化时读取本地存储的待办数据 _loadLocalTodoList(); } @override void dispose() { // 页面销毁时释放文本控制器,避免内存泄漏 _taskTextFieldController.dispose(); super.dispose(); } // 读取本地存储的待办列表 Future<void> _loadLocalTodoList() async { final prefs = await SharedPreferences.getInstance(); final savedList = prefs.getStringList(_todoStorageKey); // 如果本地有存储数据则赋值,没有则保持空列表 if (savedList != null) { setState(() { toDoItems = savedList; }); } } // 保存最新待办列表到本地 Future<void> _saveTodoList() async { final prefs = await SharedPreferences.getInstance(); await prefs.setStringList(_todoStorageKey, toDoItems); } // 抽离新增任务弹窗,减少build方法嵌套 void _showAddTaskDialog() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(15))), content: TextField( controller: _taskTextFieldController, textInputAction: TextInputAction.done, decoration: const InputDecoration(hintText: 'Task'), // 键盘点击完成直接保存 onSubmitted: (_) => _addNewTask(), ), actions: <Widget>[ TextButton( onPressed: _addNewTask, child: const Text("Save"), ), ], ); }, ); } // 抽离新增任务逻辑,统一做校验和存储 void _addNewTask() { final inputText = _taskTextFieldController.text.trim(); // 空内容不保存 if (inputText.isEmpty) { Navigator.of(context).pop(); return; } setState(() { toDoItems.add(inputText); }); // 保存最新列表到本地 _saveTodoList(); _taskTextFieldController.clear(); Navigator.of(context).pop(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: const Color.fromARGB(255, 7, 152, 70), title: const Text('Tasks'), centerTitle: true, ), body: Container( padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0), child: ListView.builder( itemCount: toDoItems.length, itemBuilder: (context, index) { return TaskContainer( child: toDoItems[index], ); })), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: FloatingActionButton( onPressed: _showAddTaskDialog, enableFeedback: true, child: const Icon(Icons.add), ), ); } }
二、代码规范&可维护性优化建议
- 不要用
// ignore:类型的注释压制警告,比如原来的prefer_const_literals_to_create_immutables警告,只需要给EdgeInsets加const修饰即可消除,从根源解决问题比忽略警告更合理 - 文本控制器一定要在
dispose生命周期里释放,否则会造成内存泄漏 - 业务逻辑尽量抽离成独立方法,不要全部嵌套在build方法里,比如弹窗、新增任务逻辑抽离后,代码可读性会大幅提升
- 所有用户输入都要做非空校验,避免用户输入全空格、空字符串的无效任务
- 常量统一管理:存储key、颜色值、尺寸、圆角这类固定值建议抽离到统一的常量文件,后续修改不需要翻业务代码
- 功能扩展预留:如果后续要实现任务勾选完成的功能,只存String类型的任务内容无法记录完成状态,建议提前封装Task实体类,包含
content(任务内容)、isCompleted(完成状态)两个字段,存储时转成Json字符串列表即可,后续加勾选、删除、编辑功能都不需要重构存储逻辑 - 状态更新要精准:
setState调用时尽量只包裹需要更新的状态变量,不要无意义的空调用,减少不必要的组件重绘
内容的提问来源于stack exchange,提问作者M.Nuh
相关产品推荐
相关产品推荐

