Flutter中如何用SharedPreferences保存多个复选框的状态?
Flutter待办清单复选框状态独立保存解决方案
我正在学习Flutter,开发一个基于sqflite本地存储的待办清单项目。在SliverList的TestListItem卡片任务项中添加了复选框,希望保存每个复选框的状态,重启应用后能恢复。但现在用SharedPreferences实现后,所有复选框状态同步——勾选一个重启后全部被勾选,取消同理。不知道该用Provider存布尔列表、sqflite新增状态列,还是用SharedPreferences就能实现,求解决方案。
当前实现代码
1. TestListItem状态类代码
class _TestListItemState extends State<TestListItem> { bool _checkbox = false; final TextEditingController updatedTaskController = TextEditingController(); final TextEditingController updatedDateController = TextEditingController(); @override void initState() { _loadCheckedValue(); } _savedCheckedValue() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { prefs.setBool("check", _checkbox); }); } _loadCheckedValue() async { SharedPreferences prefs = await SharedPreferences.getInstance(); bool? isChecked = prefs.getBool("check"); setState(() { _checkbox = isChecked ?? false; }); }
2. 复选框代码
child: CheckboxListTile( controlAffinity: ListTileControlAffinity.leading, secondary: IntrinsicWidth( child: Row(...), ), title: Text(...), subtitle: Text(...), activeColor: fabColor, value: _checkbox, onChanged: (bool? value) async { setState(() { _checkbox = !_checkbox; _savedCheckedValue(); }); }),
3. Provider代码
class TaskProvider extends ChangeNotifier { List<TaskModel> tasks = []; List<bool> checked = []; Future insertInDatabase(String activity, String date) async { final newTask = TaskModel(taskId: const Uuid().v1(), activity: activity, date: date); tasks.add(newTask); await TaskDao().save(TaskModel( taskId: newTask.taskId, activity: newTask.activity, date: newTask.date)); notifyListeners(); } Future<List<TaskModel>> readFromDatabase() async { final taskList = await TaskDao().findAll(); tasks = taskList; return tasks; } Future updateFromDatabase( String taskId, String activity, String date, ) async { final updatedTask = TaskModel(taskId: taskId, activity: activity, date: date); await TaskDao().update(updatedTask); notifyListeners(); } Future deleteFromDatabase(String taskId) async { await TaskDao().delete(taskId); notifyListeners(); } }
4. SliverList代码
child: Consumer<TaskProvider>( builder: (BuildContext context, TaskProvider provider, Widget? child) { return FutureBuilder<List<TaskModel>>( future: provider.readFromDatabase(), builder: (context, snapshot) { var items = snapshot.data; switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: case ConnectionState.active: return const Loading(); case ConnectionState.done: { if (snapshot.hasData && items != null) { if (items.isNotEmpty) { return CustomScrollView( slivers: <Widget>[ SliverToBoxAdapter( child: Text(...), ), SliverPadding( ..., sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return TestListItem( task: items[index], index: index, ); }, childCount: items.length, ), ), ), ], ); } } } } } ); } ),
问题原因
所有复选框状态同步是因为你用了同一个SharedPreferences键"check",每个任务的复选框都读写这个键,自然会互相覆盖,导致状态统一。
解决方案
方案一:在sqflite中新增状态列(推荐)
待办任务的完成状态属于任务本身的属性,和任务内容、日期一样应该存在数据库里,这是最合理的持久化方案:
- 修改TaskModel:添加
isCompleted布尔字段
class TaskModel { final String taskId; final String activity; final String date; final bool isCompleted; // 新增字段 TaskModel({ required this.taskId, required this.activity, required this.date, this.isCompleted = false, // 默认未完成 }); // 同步修改转Map和从Map解析的方法 Map<String, dynamic> toMap() { return { 'taskId': taskId, 'activity': activity, 'date': date, 'isCompleted': isCompleted ? 1 : 0, // SQLite用1/0表示布尔 }; } factory TaskModel.fromMap(Map<String, dynamic> map) { return TaskModel( taskId: map['taskId'], activity: map['activity'], date: map['date'], isCompleted: map['isCompleted'] == 1, ); } // 新增copyWith方法用于更新状态 TaskModel copyWith({bool? isCompleted}) { return TaskModel( taskId: taskId, activity: activity, date: date, isCompleted: isCompleted ?? this.isCompleted, ); } }
- 修改数据库表结构:在TaskDao的建表语句中新增
isCompleted列
// 示例建表语句 final String createTable = ''' CREATE TABLE tasks( taskId TEXT PRIMARY KEY, activity TEXT, date TEXT, isCompleted INTEGER DEFAULT 0 ) ''';
- 更新Provider方法:添加更新完成状态的逻辑
// 在TaskProvider中新增方法 Future updateTaskCompletion(String taskId, bool isCompleted) async { // 调用Dao更新数据库 await TaskDao().updateCompletion(taskId, isCompleted); // 更新本地tasks列表并通知UI刷新 final taskIndex = tasks.indexWhere((task) => task.taskId == taskId); if (taskIndex != -1) { tasks[taskIndex] = tasks[taskIndex].copyWith(isCompleted: isCompleted); notifyListeners(); } } // 同时在TaskDao中实现updateCompletion方法 Future updateCompletion(String taskId, bool isCompleted) async { final db = await DatabaseHelper.instance.database; await db.update( 'tasks', {'isCompleted': isCompleted ? 1 : 0}, where: 'taskId = ?', whereArgs: [taskId], ); }
- 修改TestListItem:直接从任务模型获取状态,修改时调用Provider方法
// _TestListItemState中无需维护_checkbox变量,直接用widget.task的状态 bool get _isChecked => widget.task.isCompleted; // 复选框部分修改 CheckboxListTile( value: _isChecked, onChanged: (bool? value) { if (value != null) { Provider.of<TaskProvider>(context, listen: false) .updateTaskCompletion(widget.task.taskId, value); } }, // 其他属性保留 )
方案二:用SharedPreferences单独存储每个任务的状态
如果暂时不想修改数据库,可以给每个任务的复选框状态分配唯一的键(用任务taskId区分):
// 修改_TestListItemState中的保存和加载方法 _savedCheckedValue() async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool("check_${widget.task.taskId}", _checkbox); } _loadCheckedValue() async { SharedPreferences prefs = await SharedPreferences.getInstance(); bool? isChecked = prefs.getBool("check_${widget.task.taskId}"); setState(() { _checkbox = isChecked ?? false; }); }
这种方法简单,但缺点是状态和任务数据分离,删除任务时需要手动清理对应的SharedPreferences键,否则会残留无效数据。
方案三:在Provider中维护布尔列表(不推荐)
这种方法把状态存在内存里,重启应用后会丢失,除非额外做持久化;而且列表索引和任务的对应关系容易出错(比如任务顺序变化时索引错位),所以不推荐使用。
内容的提问来源于stack exchange,提问作者Gabriel Pires Parreira
相关产品推荐
相关产品推荐

