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

Flutter结合SQFlite实现收藏按钮图标切换问题求助

问题解决:Flutter ListView收藏图标无法切换的问题

核心问题分析

  1. 图标状态依赖异步数据库查询,itemBuilder中直接异步获取状态会导致UI渲染异常
  2. 未维护本地状态缓存,操作数据库后未同步更新UI状态,全局setState触发全列表重建(实际仅需更新对应项状态)
  3. 数据库操作存在潜在问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 08:56:10