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

Flutter项目中Goat列表搜索功能失效问题求助

搜索逻辑无效的问题修复

问题描述

我的搜索逻辑无法正常工作,期望输入关键词后能显示过滤后的山羊列表,但实际没有效果。我是编程初学者,Add按钮的插入逻辑是硬编码的。

现有代码

HomeScreen.dart

import 'package:domingo_etr/helpers/db_helper.dart';
import 'package:domingo_etr/models/Goat.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool isSearchVisible = false;
  late FocusNode _searchFocusNode;
  TextEditingController _searchController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _searchFocusNode = FocusNode();
    _searchFocusNode.addListener(_onFocusChange);
  }

  @override
  void dispose() {
    _searchFocusNode.removeListener(_onFocusChange);
    _searchFocusNode.dispose();
    _searchController.dispose();
    super.dispose();
  }

  void _onFocusChange() {
    if (!_searchFocusNode.hasFocus) {
      print("TextField value: ${_searchController.text}");
      setState(() {
        isSearchVisible = false;
      });
      _searchController.clear();
    }
  }

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(
      const SystemUiOverlayStyle(
        statusBarColor: Colors.white,
        statusBarIconBrightness: Brightness.dark,
        systemNavigationBarColor: Colors.white,
        systemNavigationBarIconBrightness: Brightness.dark,
      ),
    );

    List<Goat> goats = [];

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: Text(
          "Apacapra",
          style: GoogleFonts.satisfy(
            color: Colors.black,
            fontSize: 30,
            fontWeight: FontWeight.w900,
          ),
        ),
        actions: [
          isSearchVisible
              ? Expanded(
                  flex: 1,
                  child: Container(
                    margin: EdgeInsets.symmetric(horizontal: 20),
                    child: TextField(
                      controller: _searchController,
                      focusNode: _searchFocusNode,
                      decoration: InputDecoration(
                        hintText: "Search by name",
                        hintStyle: TextStyle(fontWeight: FontWeight.normal),
                        suffix: GestureDetector(
                          onTap: () {
                            _searchFocusNode.unfocus();
                          },
                          child: FaIcon(
                            FontAwesomeIcons.xmark,
                            size: 20,
                            color: Colors.black,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderSide:
                              BorderSide(color: Colors.black, width: 1.0),
                        ),
                        enabledBorder: OutlineInputBorder(),
                      ),
                      onChanged: (value) {
                        setState(() {
                          // Update the existing list based on the entered text
                          goats = goats
                              .where((goat) =>
                                  goat.pasternId
                                      .toLowerCase()
                                      .contains(value.toLowerCase()) ||
                                  goat.breed
                                      .toLowerCase()
                                      .contains(value.toLowerCase()) ||
                                  goat.name
                                      .toLowerCase()
                                      .contains(value.toLowerCase()))
                              .toList();
                        });
                      },
                    ),
                  ),
                )
              : IconButton(
                  onPressed: () {
                    setState(() {
                      isSearchVisible = true;
                    });
                    _searchFocusNode.requestFocus();
                  },
                  icon: FaIcon(
                    FontAwesomeIcons.magnifyingGlass,
                    size: 20,
                    color: Colors.black,
                  ),
                ),
        ],
      ),
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: FutureBuilder(
                future: DbHelper.getAllGoats(),
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    // While the Future is still running, display a loading indicator
                    return Center(
                      child: CircularProgressIndicator(),
                    );
                  } else if (snapshot.hasError) {
                    // Handle errors
                    return Text('Error: ${snapshot.error}');
                  } else if (snapshot.data == null) {
                    // Handle the case where snapshot.data is null
                    return Text('No data available');
                  } else {
                    // Data is available, proceed with displaying it
                    goats = snapshot.data!.toList();

                    return ListView.builder(
                      itemCount: goats.length,
                      itemBuilder: (context, index) {
                        final goat = goats[index];
                        return ListTile(
                          title: Text('ID: ${goat.pasternId}'),
                          subtitle: Text(
                            'Breed: ${goat.breed}, Weight: ${goat.currentWeight}, Name: ${goat.name}',
                          ),
                        );
                      },
                    );
                  }
                },
              ),
            ),
            ElevatedButton(
              onPressed: () {
                final goat = Goat(
                  pasternId: "GT-12",
                  name: "Nene",
                  breed: "Anglo-Nubian",
                  dateOfBirth: "2019-02-12",
                  currentWeight: 40,
                );
                DbHelper.insertGoat(goat);
                setState(() {});
              },
              child: Text("Add"),
            )
          ],
        ),
      ),
    );
  }
}

db_helper.dart

import 'package:domingo_etr/models/Goat.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

class DbHelper {
  // Constants
  static const dbName = 'goat_management.db';
  static const dbVersion = 1;

  // Table constants
  static const tbGoat = 'Goat';
  static const tbVaccine = 'Vaccine';
  static const tbVitamin = 'Vitamin';

  // Columns for the 'Goat' table
  static const colGoatId = 'goatId';
  static const colPasternId = 'pasternId';
  static const colName = 'name';
  static const colBreed = 'breed';
  static const colDateOfBirth = 'dateOfBirth';
  static const colCurrentWeight = 'currentWeight';

  // Columns for the 'Vaccine' table
  static const colVaccineId = 'vaccineId';
  static const colVaccineGoatId = 'goatId'; // Foreign key reference
  static const colVaccineName = 'vaccineName';
  static const colVaccineBrandName = 'brandName';
  static const colVaccineType = 'type';
  static const colVaccineDosage = 'dosage';

  // Columns for the 'Vitamin' table
  static const colVitaminId = 'vitaminId';
  static const colVitaminGoatId = 'goatId'; // Foreign key reference
  static const colVitaminName = 'vitaminName';
  static const colVitaminBrandName = 'brandName';
  static const colVitaminType = 'type';
  static const colVitaminDosage = 'dosage';

  // Methods for db

  static Future<Database> openDB() async {
    var path = join(await getDatabasesPath(), DbHelper.dbName);
    var db = await openDatabase(
      path,
      version: DbHelper.dbVersion,
      onCreate: (db, version) {
        _createTables(db);
      },
      onUpgrade: (db, oldVersion, newVersion) {
        _upgradeTables(db, oldVersion, newVersion);
      },
    );
    return db;
  }

  static void _createTables(Database db) {
    // Create the 'Goat' table
    var goatTableSql = '''
      CREATE TABLE IF NOT EXISTS $tbGoat(
        $colGoatId INTEGER PRIMARY KEY AUTOINCREMENT,
        $colPasternId TEXT NOT NULL UNIQUE,
        $colName TEXT NOT NULL,
        $colBreed TEXT NOT NULL,
        $colDateOfBirth TEXT NOT NULL,
        $colCurrentWeight INTEGER NOT NULL
      )
    ''';
    db.execute(goatTableSql);

    // Create the 'Vaccine' table
    var vaccineTableSql = '''
      CREATE TABLE IF NOT EXISTS $tbVaccine(
        $colVaccineId INTEGER PRIMARY KEY AUTOINCREMENT,
        $colVaccineGoatId INTEGER REFERENCES $tbGoat($colGoatId),
        $colVaccineName TEXT NOT NULL,
        $colVaccineBrandName TEXT NOT NULL,
        $colVaccineType TEXT NOT NULL,
        $colVaccineDosage TEXT NOT NULL
      )
    ''';
    db.execute(vaccineTableSql);

    // Create the 'Vitamin' table
    var vitaminTableSql = '''
      CREATE TABLE IF NOT EXISTS $tbVitamin(
        $colVitaminId INTEGER PRIMARY KEY AUTOINCREMENT,
        $colVitaminGoatId INTEGER REFERENCES $tbGoat($colGoatId),
        $colVitaminName TEXT NOT NULL,
        $colVitaminBrandName TEXT NOT NULL,
        $colVitaminType TEXT NOT NULL,
        $colVitaminDosage INTEGER NOT NULL
      )
    ''';
    db.execute(vitaminTableSql);
  }

  static void _upgradeTables(Database db, int oldVersion, int newVersion) {
    // Handle table upgrades here
    if (newVersion <= oldVersion) return;

    // Drop existing tables and recreate them
    db.execute('DROP TABLE IF EXISTS $tbGoat');
    db.execute('DROP TABLE IF EXISTS $tbVaccine');
    db.execute('DROP TABLE IF EXISTS $tbVitamin');

    // Recreate tables
    _createTables(db);
  }

  // Methods na CRUD Ops -----------------

  // Get a goat
  static Future<List<Goat>> getAllGoats() async {
    final Database db = await openDB();
    final List<Map<String, dynamic>> maps = await db.query(tbGoat);

    return List.generate(maps.length, (i) {
      return Goat(
        goatId: maps[i]['goatId'],
        pasternId: maps[i]['pasternId'],
        name: maps[i]['name'],
        breed: maps[i]['breed'],
        dateOfBirth: maps[i]['dateOfBirth'],
        currentWeight: maps[i]['currentWeight'],
      );
    });
  }

  // Insert a goat record
  static Future<void> insertGoat(Goat goat) async {
    final Database db = await openDB();

    await db.insert(
      tbGoat,
      goat.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }
}

问题原因

  1. goats变量作用域错误:在build方法里定义List<Goat> goats = [];,每次setState()触发重建时,变量会被重新初始化,且FutureBuilder会重新执行DbHelper.getAllGoats(),覆盖过滤后的结果。
  2. 过滤逻辑基于已过滤数据:onChanged中用goats = goats.where(...).toList(),每次输入都在已过滤列表上再次过滤,导致搜索范围越来越小,重建后过滤状态丢失。
  3. FutureBuilder的future直接写在build中:每次build都会重新请求数据库,浪费资源且重置数据。

修复方案

修改_HomeScreenState类,拆分原始数据源和过滤后数据为成员变量,优化数据请求逻辑:

修改后的HomeScreen代码

import 'package:domingo_etr/helpers/db_helper.dart';
import 'package:domingo_etr/models/Goat.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool isSearchVisible = false;
  late FocusNode _searchFocusNode;
  TextEditingController _searchController = TextEditingController();
  
  // 保存原始所有山羊数据
  List<Goat> _allGoats = [];
  // 保存过滤后的山羊数据,用于显示
  List<Goat> _filteredGoats = [];
  // 保存数据请求的Future,避免重复调用
  late Future<List<Goat>> _goatsFuture;

  @override
  void initState() {
    super.initState();
    _searchFocusNode = FocusNode();
    _searchFocusNode.addListener(_onFocusChange);
    // 初始化数据请求
    _goatsFuture = _fetchAllGoats();
  }

  @override
  void dispose() {
    _searchFocusNode.removeListener(_onFocusChange);
    _searchFocusNode.dispose();
    _searchController.dispose();
    super.dispose();
  }

  // 获取所有山羊数据并初始化原始列表
  Future<List<Goat>> _fetchAllGoats() async {
    final goats = await DbHelper.getAllGoats();
    setState(() {
      _allGoats = goats;
      _filteredGoats = goats;
    });
    return goats;
  }

  void _onFocusChange() {
    if (!_searchFocusNode.hasFocus) {
      print("TextField value: ${_searchController.text}");
      setState(() {
        isSearchVisible = false;
        // 清空搜索框并恢复原始列表
        _searchController.clear();
        _filteredGoats = _allGoats;
      });
    }
  }

  // 处理搜索过滤逻辑
  void _filterGoats(String query) {
    if (query.isEmpty) {
      _filteredGoats = _allGoats;
    } else {
      final lowerQuery = query.toLowerCase();
      _filteredGoats = _allGoats.where((goat) =>
          goat.pasternId.toLowerCase().contains(lowerQuery) ||
          goat.breed.toLowerCase().contains(lowerQuery) ||
          goat.name.toLowerCase().contains(lowerQuery)).toList();
    }
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(
      const SystemUiOverlayStyle(
        statusBarColor: Colors.white,
        statusBarIconBrightness: Brightness.dark,
        systemNavigationBarColor: Colors.white,
        systemNavigationBarIconBrightness: Brightness.dark,
      ),
    );

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: Text(
          "Apacapra",
          style: GoogleFonts.satisfy(
            color: Colors.black,
            fontSize: 30,
            fontWeight: FontWeight.w900,
          ),
        ),
        actions: [
          isSearchVisible
              ? Expanded(
                  flex: 1,
                  child: Container(
                    margin: EdgeInsets.symmetric(horizontal: 20),
                    child: TextField(
                      controller: _searchController,
                      focusNode: _searchFocusNode,
                      decoration: InputDecoration(
                        hintText: "Search by name",
                        hintStyle: TextStyle(fontWeight: FontWeight.normal),
                        suffix: GestureDetector(
                          onTap: () {
                            _searchFocusNode.unfocus();
                          },
                          child: FaIcon(
                            FontAwesomeIcons.xmark,
                            size: 20,
                            color: Colors.black,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderSide:
                              BorderSide(color: Colors.black, width: 1.0),
                        ),
                        enabledBorder: OutlineInputBorder(),
                      ),
                      onChanged: _filterGoats, // 调用过滤方法
                    ),
                  ),
                )
              : IconButton(
                  onPressed: () {
                    setState(() {
                      isSearchVisible = true;
                    });
                    _searchFocusNode.requestFocus();
                  },
                  icon: FaIcon(
                    FontAwesomeIcons.magnifyingGlass,
                    size: 20,
                    color: Colors.black,
                  ),
                ),
        ],
      ),
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: FutureBuilder(
                future: _goatsFuture, // 使用成员变量的Future
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return Center(
                      child: CircularProgressIndicator(),
                    );
                  } else if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                  }
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 23:49:47