Flutter结合SQFlite实现收藏按钮图标切换问题求助
问题解决:Flutter ListView收藏图标无法切换的问题
核心问题分析
- 图标状态依赖异步数据库查询,
itemBuilder中直接异步获取状态会导致UI渲染异常 - 未维护本地状态缓存,操作数据库后未同步更新UI状态,全局
setState触发全列表重建(实际仅需更新对应项状态) - 数据库操作存在潜在问题:
checkF用字符串拼接有SQL注入风险,remove用LIKE可能误删匹配项
解决方案步骤
1. 维护本地收藏状态缓存
在页面State中添加Set<String>存储已收藏的单词key,实现同步判断图标状态:
Set<String> favoriteKeys = {};
2. 初始化加载已收藏数据
在initState中从数据库拉取所有收藏项,填充到本地缓存:
@override void initState() { super.initState(); filteredEntries = myMap.entries.toList(); _loadFavorites(); } Future<void> _loadFavorites() async { List<ReadyDict> favorites = await DatabaseHelper.instance.getItems(); setState(() { favoriteKeys = Set.from(favorites.map((item) => item.word)); }); }
3. 修改IconButton逻辑
通过本地缓存判断图标状态,操作数据库后同步更新缓存并刷新UI:
trailing: IconButton( onPressed: () async { if (favoriteKeys.contains(key)) { await DatabaseHelper.instance.remove(key); setState(() { favoriteKeys.remove(key); }); } else { await DatabaseHelper.instance.add(ReadyDict(word: key, description: value)); setState(() { favoriteKeys.add(key); }); } }, icon: Icon( favoriteKeys.contains(key) ? Icons.favorite : Icons.favorite_border, color: Colors.red, ), ),
4. 修复数据库操作问题
修改DatabaseHelper中的方法,避免SQL注入并保证操作准确性:
// 修复收藏检查逻辑,用参数绑定 Future<bool> checkF(String word) async { final db = await instance.database; final result = await db.query( 'words', where: 'word = ?', whereArgs: [word], limit: 1, ); return result.isNotEmpty; } // 修复删除逻辑,精确匹配word Future<int> remove(String word) async { Database db = await instance.database; return await db.delete('words', where: 'word = ?', whereArgs: [word]); }
5. 移除无用的FutureBuilder
原代码中绑定空方法的FutureBuilder可直接替换为ListView.builder,简化代码:
body: ListView.builder( itemCount: filteredEntries.length, itemBuilder: (BuildContext context, int index) { // 原有item逻辑 }, ),
修改后的完整代码
搜索页面代码
import 'package:flutter/material.dart'; import 'package:testshared/database/sql_helper.dart'; class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { Map<String, String> myMap = { "key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4", "key5": "value5", "key6": "value1", "key7": "value2", "key8": "value3", "key9": "value4", "key10": "value5", "key11": "value1", "key12": "value2", "key13": "value3", "key14": "value4", "key15": "value5", }; String searchQuery = ''; List<MapEntry<String, String>> filteredEntries = []; Set<String> favoriteKeys = {}; // 本地收藏状态缓存 @override void initState() { super.initState(); filteredEntries = myMap.entries.toList(); _loadFavorites(); } Future<void> _loadFavorites() async { List<ReadyDict> favorites = await DatabaseHelper.instance.getItems(); setState(() { favoriteKeys = Set.from(favorites.map((item) => item.word)); }); } @override Widget build(BuildContext context) { return MaterialApp( color: Colors.greenAccent[700], home: Scaffold( appBar: AppBar( titleSpacing: 3, backgroundColor: Colors.greenAccent[700], title: _search(), ), body: ListView.builder( itemCount: filteredEntries.length, itemBuilder: (BuildContext context, int index) { MapEntry<String, String> entry = filteredEntries[index]; String key = entry.key; String value = entry.value; return ListTile( title: RichText( text: TextSpan( children: [ TextSpan( text: key, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 25, color: Colors.red), ), ], ), ), trailing: IconButton( onPressed: () async { if (favoriteKeys.contains(key)) { await DatabaseHelper.instance.remove(key); setState(() { favoriteKeys.remove(key); }); } else { await DatabaseHelper.instance.add(ReadyDict(word: key, description: value)); setState(() { favoriteKeys.add(key); }); } }, icon: Icon( favoriteKeys.contains(key) ? Icons.favorite : Icons.favorite_border, color: Colors.red, ), ), subtitle: RichText( text: TextSpan( children: [ TextSpan( text: value, style: const TextStyle( fontSize: 20, color: Colors.black, ), ), ], ), ), ); }, ), ), ); } Widget _search() { return TextField( decoration: const InputDecoration( contentPadding: EdgeInsets.all(5), filled: true, fillColor: Colors.white, hintText: 'enter', border: OutlineInputBorder( borderRadius: BorderRadius.all( Radius.circular(10.0), ), ), ), onSubmitted: (query) { _sout(query); }, ); } void _sout(String query) { setState(() { searchQuery = query; filteredEntries = myMap.entries .where((entry) => entry.key.contains(searchQuery) || entry.value.contains(searchQuery)) .toList(); }); } }
SQFlite页面代码
import 'package:sqflite/sqflite.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart'; import 'dart:io'; class ReadyDict { final int? id; final String word; final String description; ReadyDict({this.id, required this.word, required this.description}); factory ReadyDict.fromMap(Map<String, dynamic> json) => ReadyDict( id: json['id'], word: json['word'], description: json['description'], ); Map<String, dynamic> toMap() { return { 'word': word, 'description': description, }; } } class DatabaseHelper { DatabaseHelper._privateConstructor(); static final DatabaseHelper instance = DatabaseHelper._privateConstructor(); static Database? _database; Future<Database> get database async => _database ??= await _initDatabase(); Future<Database> _initDatabase() async { Directory documentsDirectory = await getApplicationDocumentsDirectory(); String path = join(documentsDirectory.path, 'words.db'); return await openDatabase( path, version: 1, onCreate: _onCreate, ); } Future _onCreate(Database db, int version) async { await db.execute(""" CREATE TABLE words( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, word TEXT, description TEXT, createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)"""); } Future<List<ReadyDict>> getItems() async { Database db = await instance.database; var words = await db.query('words', orderBy: 'id DESC'); List<ReadyDict> wordsList = words.isNotEmpty ? words.map((c) => ReadyDict.fromMap(c)).toList() : []; return wordsList; } Future<int> add(ReadyDict readyDict) async { Database db = await instance.database; return await db.insert('words', readyDict.toMap()); } Future<int> remove(String word) async { Database db = await instance.database; return await db.delete('words', where: 'word = ?', whereArgs: [word]); } Future<bool> checkF(String word) async { final db = await instance.database; final result = await db.query( 'words', where: 'word = ?', whereArgs: [word], limit: 1, ); return result.isNotEmpty; } }
内容的提问来源于stack exchange,提问作者Baysangur
相关产品推荐
相关产品推荐

