Flutter中Autocomplete组件重建时为何不更新建议列表?
问题原因分析
你的问题核心在于Autocomplete组件的optionsBuilder回调触发机制:这个回调只会在文本编辑控制器的内容发生变化时自动执行,而Bloc状态更新导致的BlocBuilder重建,并不会主动触发optionsBuilder重新计算选项。
虽然BlocBuilder每次状态变化都会重新构建Autocomplete,但只要文本输入框的内容没有改变,Autocomplete就不会调用optionsBuilder,也就不会读取新的suggestions数据,导致建议列表无法更新。
修复方案
这里提供两种有效修复方式,你可以根据需求选择:
方式一:让optionsBuilder直接读取Bloc最新状态
修改optionsBuilder,不再依赖build方法中的suggestions变量,而是直接在回调内获取Bloc的当前状态。这样每次optionsBuilder被调用时,都会拿到最新的建议数据:
optionsBuilder: (TextEditingValue textEditingValue) { // 直接从Bloc读取当前状态 final currentState = context.read<PlaceResearchBloc>().state; List<String> suggestions = []; if (currentState is PlaceSuggestionsLoaded) { suggestions = currentState.placeSuggestions.map((p) => p.name).toList(); } else if (currentState is PlaceSuggestionsError) { // 可以在这里处理错误状态,比如返回空列表或提示文本 suggestions = []; } print("Autocomplete suggestions : $suggestions"); return suggestions.where( (s) => s.toLowerCase().contains(textEditingValue.text.toLowerCase()), ); },
方式二:通过Key强制Autocomplete重建
给Autocomplete添加一个与suggestions绑定的Key,当suggestions变化时,Key也会变化,从而强制Autocomplete重新实例化并调用optionsBuilder:
builder: (context, state) { List<String> suggestions = []; Key autocompleteKey = const ValueKey("initial"); if (state is PlaceSuggestionsLoaded) { suggestions = state.placeSuggestions.map((p) => p.name).toList(); // 用suggestions列表作为Key,确保只有数据变化时才重建 autocompleteKey = ValueKey<List<String>>(suggestions); } else if (state is PlaceSuggestionsError) { autocompleteKey = ValueKey<String>(state.message); } print("Bloc builder Suggestions : $suggestions"); return Autocomplete<String>( key: autocompleteKey, // 添加绑定的Key optionsBuilder: (TextEditingValue textEditingValue) { print("Autocomplete suggestions : $suggestions"); return suggestions.where( (s) => s.toLowerCase().contains(textEditingValue.text.toLowerCase()), ); }, // 其他属性保持不变 ); }
完整修复后的代码
结合方式一(更优雅,无需强制重建组件),同时添加加载状态提示,完整代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class Place { final String name; Place({required this.name}); } abstract class PlaceResearchState {} class PlaceResearchInitial extends PlaceResearchState {} class PlaceSuggestionsLoading extends PlaceResearchState {} class PlaceSuggestionsLoaded extends PlaceResearchState { final List<Place> placeSuggestions; PlaceSuggestionsLoaded(this.placeSuggestions); } class PlaceSuggestionsError extends PlaceResearchState { final String message; PlaceSuggestionsError(this.message); } class PlaceResearchBloc extends Cubit<PlaceResearchState> { PlaceResearchBloc() : super(PlaceResearchInitial()); void searchSuggestions(String query) { if (query.isEmpty) { emit(PlaceResearchInitial()); return; } emit(PlaceSuggestionsLoading()); Future.delayed(const Duration(milliseconds: 500), () { final suggestions = _fetchPlaceSuggestions(query); if (suggestions.isNotEmpty) { emit(PlaceSuggestionsLoaded(suggestions)); } else { emit(PlaceSuggestionsError("No suggestion found")); } }); } List<Place> _fetchPlaceSuggestions(String query) { List<Place> allPlaces = [ const Place(name: 'New York'), const Place(name: 'Paris'), const Place(name: 'London'), const Place(name: 'Hamburg'), const Place(name: 'Barcelona') ]; return allPlaces.where((place) => place.name.toLowerCase().contains(query.toLowerCase())).toList(); } } class SearchBarWithBlocListener extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Autocomplete MRE")), body: Stack( children: [ Positioned( top: 20, left: 10, right: 10, child: Material( elevation: 4, borderRadius: BorderRadius.circular(8), child: BlocProvider( create: (_) => PlaceResearchBloc(), child: BlocBuilder<PlaceResearchBloc, PlaceResearchState>( builder: (context, state) { // 处理加载状态,显示loading指示器 Widget? loadingIndicator; if (state is PlaceSuggestionsLoading) { loadingIndicator = const Positioned( right: 10, top: 8, child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ); } return Stack( children: [ Autocomplete<String>( optionsBuilder: (TextEditingValue textEditingValue) { final currentState = context.read<PlaceResearchBloc>().state; List<String> suggestions = []; if (currentState is PlaceSuggestionsLoaded) { suggestions = currentState.placeSuggestions.map((p) => p.name).toList(); } print("Autocomplete suggestions : $suggestions"); return suggestions.where( (s) => s.toLowerCase().contains(textEditingValue.text.toLowerCase()), ); }, displayStringForOption: (String option) => option, onSelected: (String selection) { print('Selected suggestion : $selection'); }, fieldViewBuilder: (BuildContext context, TextEditingController fieldTextEditingController, FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { return TextField( controller: fieldTextEditingController, focusNode: fieldFocusNode, onChanged: (value) { context.read<PlaceResearchBloc>().searchSuggestions(value); }, decoration: const InputDecoration( hintText: "Search for a place", border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 10), ), ); }, ), if (loadingIndicator != null) loadingIndicator, ], ); }, ), ), ), ), ], ), ); } }
内容的提问来源于stack exchange,提问作者Eli
相关产品推荐
相关产品推荐

