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

Flutter 页面重载时ListView重复加载Sqflite数据如何解决

问题根源
  • 存在重复数据库查询逻辑:initState生命周期中调用getAllTodos向TodoProvider的缓存列表写入数据,同时build方法内的FutureBuilder又独立调用了一次全量查询接口,每次页面重新打开、组件重建时initState会重新执行,重复触发数据写入。
  • 数据写入前未清空旧缓存:getAllTodos方法查询到数据后,直接通过add方法向Provider的列表中追加元素,没有先清空列表内已存在的旧数据,每打开一次页面就会追加一次全量数据,直接导致列表重复展示。
  • 数据源逻辑冲突:同时维护了FutureBuilder、Provider两套数据来源,FutureBuilder查询到的结果实际没有参与渲染,白白消耗性能,还容易触发不必要的组件重建。
  • 误区说明:尝试通过dispose释放方法解决问题的思路不成立,问题本质是数据重复追加,不是方法或内存没有被释放。
修复步骤
  • 移除冗余的查询逻辑:统一使用TodoProvider管理待办列表数据,删除FutureBuilder中重复的数据库查询调用,避免两套数据源冲突。
  • 优化getAllTodos逻辑:每次查询全量数据前,先清空Provider中已缓存的旧列表,再写入新查询到的数据,从根源避免重复追加。
  • 修正列表取值逻辑:原代码中同时出现了value.newTodo、value.todoitem两个列表取值,统一使用同一个列表属性渲染,避免取值混乱。
修正后代码
class TodoList extends StatefulWidget {
  const TodoList({Key? key}) : super(key: key);

  @override
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {

  getAllTodos() async {
    List<TodoModel> _todoList = await TodoDatabase.instance.queryAlltodos();
    // 先清空旧数据再写入新数据
    context.read<TodoProvider>().todoitem.clear();
    _todoList.forEach((todo) {
      var todoModel = TodoModel(
          title: todo.title,
          description: todo.description,
          created: todo.created,
          isChecked: todo.isChecked);
      context.read<TodoProvider>().todoitem.add(todoModel);
    });
    // 通知UI刷新
    context.read<TodoProvider>().notifyListeners();
  }

  @override
  void initState() {
    super.initState();
    // 帧渲染完成后再执行数据查询,避免上下文未就绪问题
    WidgetsBinding.instance.addPostFrameCallback((_) {
      getAllTodos();
    });
  }

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    final theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Consumer<TodoProvider>(
          builder: (context, value, child) => (value.todoitem.isEmpty)
              ? const Center(child: Text('No Todos'))
              : ListView.separated(
                  itemCount: value.todoitem.length,
                  physics: const BouncingScrollPhysics(),
                  separatorBuilder: (context, index) => SizedBox(
                        height: size.height * 0.01,
                      ),
                  itemBuilder: (_, i) =>  Slidable(
                        key: ValueKey(value.todoitem[i].id), // 用待办唯一ID当key,不要用索引
                        startActionPane: ActionPane(
                          motion: StretchMotion(),
                          children: [
                            SlidableAction(
                              backgroundColor: Colors.green,
                              foregroundColor: Colors.black,
                              icon: Icons.edit,
                              label: 'Edit',
                              onPressed: (context) async {
                                TodoModel todo = value.todoitem[i];
                                editTodo(context, todo);
                                showSnackBar(
                                    context, 'Edit ${todo.title}?');
                              },
                            )
                          ],
                        ),
                        endActionPane: ActionPane(
                          motion: ScrollMotion(),
                          children: [
                            SlidableAction(
                                backgroundColor: Colors.red,
                                foregroundColor: Colors.black,
                                spacing: 0,
                                icon: Icons.delete,
                                label: 'delete',
                                onPressed: (context) async {
                                  showDialog(
                                      context: context,
                                      builder: (context) {
                                        return AlertDialog(
                                            shape: RoundedRectangleBorder(
                                              borderRadius:
                                                  BorderRadius.circular(
                                                      20),
                                            ),
                                            title:
                                                const Text('Delete this TODO',style: TextStyle(color: Colors.amber)),
                                            content:
                                                const Text('Are you Sure?'),
                                            actions: [
                                              TextButton(
                                                child: const Text('No',style: TextStyle(color: Colors.amber)),
                                                onPressed: () {
                                                  Navigator.pop(context);
                                                },
                                              ),
                                              TextButton(
                                                  child: const Text('Yes',style: TextStyle(color: Colors.amber)),
                                                  onPressed: () async {
                                                    TodoModel todo =
                                                        value.todoitem[i];
                                                    String result =
                                                        await context
                                                            .read<
                                                                TodoProvider>()
                                                            .deleteToDo(
                                                                todo);
                                                    if (result != 'OK!') {
                                                      return showSnackBar(
                                                          context,
                                                          result);
                                                    } else {
                                                      showSnackBar(
                                                          context,
                                                          'Deleted');
                                                      Navigator.pop(
                                                          context);
                                                    }
                                                  })
                                            ]);
                                      });
                                })
                          ],
                        ),
                        child: InkWell(
                          onTap: () {},
                          child: ListTile(
                            tileColor: theme.primaryColorLight,
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(20),
                            ),
                            leading: Checkbox(
                              shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(3)),
                              value: value.todoitem[i].isChecked,
                              activeColor: theme.primaryColorDark,
                              checkColor: theme.primaryColorLight,
                              onChanged: (_) async {
                                TodoModel todo = value.todoitem[i];
                                await context
                                    .read<TodoProvider>()
                                    .updateTodo(todo);
                              },
                            ),
                            title: Text(value.todoitem[i].title),
                            subtitle: Text(value.todoitem[i].description),
                          ),
                        ),
                      ))),
    );
  }
}

void editTodo(BuildContext context, TodoModel todo) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => EditScreen(todo: todo),
    ),
  );
}

额外优化点:Slidable的key不要用列表索引,改用待办数据的唯一ID,避免列表增删时出现状态错乱问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 22:15:30