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

Flutter ListView搜索结果不更新问题排查求助

问题排查:ListView搜索过滤后不刷新

现有代码实现ListView通配符搜索功能,已通过打印确认_runFilter函数正常执行且能得到正确的过滤结果,但ListView未更新,同时StreamBuilder内的print("line 119: ...")无输出。

原代码

class ItemList extends StatelessWidget {
  ItemList({Key? key}) : super(key: key) {
    _stream = _reference.snapshots();
    _foundItems = items; // Initialize _foundItems with items
  }

  final Query _reference = FirebaseFirestore.instance.collection('collection_name').orderBy(
      "dlastupd", descending: true);
  late final Stream<QuerySnapshot> _stream;
  late List<Map<dynamic, dynamic>> _foundItems;
  final List<Map<dynamic, dynamic>> items = [];

  void _runFilter(String enteredKeyword) {
    print("line 63: _runFilter ran");
    if (enteredKeyword.isEmpty) {
      // if the search field is empty or only contains white space
      _foundItems = items;
      print("line 67 unfiltered");
    } else {
      _foundItems = items
          .where((item) =>
          item["name"].toLowerCase().contains(enteredKeyword.toLowerCase()))
          .toList();
    }
    print("line 73 filtered: ");
    print(_foundItems);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Updates'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(18.0),
            child: TextField(
              onChanged: (value) => _runFilter(value),
              decoration: InputDecoration(
                  labelText: 'Search', suffixIcon: Icon(Icons.search)),
            ),
          ),
          const SizedBox(
            height: 20,
          ),
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: _stream,
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                if (snapshot.hasError) {
                  return Center(child: Text('Some error occurred ${snapshot.error}'));
                }
                if (snapshot.hasData) {
                  QuerySnapshot querySnapshot = snapshot.data;
                  List<QueryDocumentSnapshot> documents = querySnapshot.docs;
                  items.clear(); // Clear items to avoid duplication
                  items.addAll(documents.map((e) =>
                  {
                    'name': e['name'],
                    'country': e['country'],
                    'dlastupd': e['dlastupd'],
                    'dlastupd date': DateTime.fromMillisecondsSinceEpoch(
                        e['dlastupd'] * 1000),
                    'id': e['id'],
                    'type': e['type'],
                  }));
                  _runFilter(""); // Filter items initially with an empty string
                  print("line 119: filtered items before listview builder:");
                  print(_foundItems);
                  return ListView.builder(
                      itemCount: _foundItems.length,
                      itemBuilder: (BuildContext context, int index) {
                        Map thisItem = _foundItems[index];
                        return ListTile(
                          title: Text.rich(
                              TextSpan(
                                  children: [
                                    TextSpan(text: formatDate(thisItem['dlastupd date'], [d, '', M]) + " " + thisItem['country'] + " (" + thisItem['type'][0] + thisItem['type'][1] + ") - " + thisItem['name'] + " " ,
                                        style: TextStyle(fontWeight: FontWeight.normal)),
                                  ]
                              )
                          ),
                          onTap: () {
                            Navigator.pushNamed(
                              context,
                              '/factfile',
                              arguments: {
                                'id': thisItem['id'],
                              },
                            );
                          },
                        );
                      });
                }
                return Center(child: CircularProgressIndicator());
              },
            ),
          ),
        ],
      ),
    );
  }
}

核心原因

  1. StatelessWidget无法触发UI重建
    ItemList是StatelessWidget,修改_foundItems变量后,框架不会自动重新执行build方法,ListView始终使用旧的_foundItems数据,不会更新。

  2. StreamBuilder的builder仅在Stream有新快照时执行
    搜索时调用_runFilter不会触发Stream产生新事件,因此StreamBuilder的builder方法不会重新执行,也就不会打印line 119的日志,ListView内容也不会基于新的_foundItems重建。

  3. 可变状态管理不当
    在StatelessWidget中使用late修饰的可变列表_foundItems和items,不符合StatelessWidget的设计规范,容易导致状态不一致。

修复方案

将ItemList改为StatefulWidget,通过setState()管理状态变化,触发UI刷新。以下是修改后的完整代码:

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

  @override
  State<ItemList> createState() => _ItemListState();
}

class _ItemListState extends State<ItemList> {
  final Query _reference = FirebaseFirestore.instance.collection('collection_name').orderBy(
      "dlastupd", descending: true);
  late Stream<QuerySnapshot> _stream;
  List<Map<dynamic, dynamic>> _foundItems = [];
  List<Map<dynamic, dynamic>> items = [];

  @override
  void initState() {
    super.initState();
    // 在initState中初始化Stream,替代构造函数初始化
    _stream = _reference.snapshots();
  }

  void _runFilter(String enteredKeyword) {
    print("line 63: _runFilter ran");
    List<Map<dynamic, dynamic>> result;
    if (enteredKeyword.isEmpty) {
      // 创建新列表避免引用同一内存地址
      result = List.from(items);
      print("line 67 unfiltered");
    } else {
      result = items
          .where((item) =>
              item["name"].toLowerCase().contains(enteredKeyword.toLowerCase()))
          .toList();
    }
    // 调用setState触发UI重建
    setState(() {
      _foundItems = result;
    });
    print("line 73 filtered: ");
    print(_foundItems);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Updates'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(18.0),
            child: TextField(
              onChanged: (value) => _runFilter(value),
              decoration: const InputDecoration(
                  labelText: 'Search', suffixIcon: Icon(Icons.search)),
            ),
          ),
          const SizedBox(height: 20),
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: _stream,
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                if (snapshot.hasError) {
                  return Center(child: Text('Some error occurred ${snapshot.error}'));
                }
                if (snapshot.hasData) {
                  QuerySnapshot querySnapshot = snapshot.data;
                  List<QueryDocumentSnapshot> documents = querySnapshot.docs;
                  // 将Firestore数据转换为本地列表
                  final newItems = documents.map((e) => {
                    'name': e['name'],
                    'country': e['country'],
                    'dlastupd': e['dlastupd'],
                    'dlastupd date': DateTime.fromMillisecondsSinceEpoch(
                        e['dlastupd'] * 1000),
                    'id': e['id'],
                    'type': e['type'],
                  }).toList();
                  
                  // 更新items并触发初始化过滤
                  setState(() {
                    items = newItems;
                  });
                  _runFilter("");
                  print("line 119: filtered items before listview builder:");
                  print(_foundItems);
                  
                  return ListView.builder(
                      itemCount: _foundItems.length,
                      itemBuilder: (BuildContext context, int index) {
                        Map thisItem = _foundItems[index];
                        return ListTile(
                          title: Text.rich(
                            TextSpan(
                              children: [
                                TextSpan(
                                  text: "${formatDate(thisItem['dlastupd date'], [d, '', M])} ${thisItem['country']} (${thisItem['type'][0]}${thisItem['type'][1]}) - ${thisItem['name']} ",
                                  style: const TextStyle(fontWeight: FontWeight.normal),
                                ),
                              ]
                            )
                          ),
                          onTap: () {
                            Navigator.pushNamed(
                              context,
                              '/factfile',
                              arguments: {'id': thisItem['id']},
                            );
                          },
                        );
                      });
                }
                return const Center(child: CircularProgressIndicator());
              },
            ),
          ),
        ],
      ),
    );
  }
}

关键修改点

  • 改为StatefulWidget:只有StatefulWidget能通过setState()触发UI重建,解决变量变化不更新UI的问题。
  • 状态移至State类:将items、_foundItems等可变状态放在State类中,符合Flutter的状态管理规范。
  • _runFilter中调用setState:修改_foundItems后调用setState(),通知框架更新ListView。
  • 优化Stream数据处理:创建新列表newItems替代直接修改items,避免状态不一致;通过setState()更新items,确保数据变化能触发后续逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 02:14:58