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

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中新增状态列(推荐)

待办任务的完成状态属于任务本身的属性,和任务内容、日期一样应该存在数据库里,这是最合理的持久化方案:

  1. 修改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,
    );
  }
}
  1. 修改数据库表结构:在TaskDao的建表语句中新增isCompleted列
// 示例建表语句
final String createTable = '''
CREATE TABLE tasks(
  taskId TEXT PRIMARY KEY,
  activity TEXT,
  date TEXT,
  isCompleted INTEGER DEFAULT 0
)
''';
  1. 更新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],
  );
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 21:59:55