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
相关产品推荐
相关产品推荐

