如何在Flutter中用flutter_riverpod实现搜索框并修复报错
Flutter Riverpod 搜索框实现问题排查与解决方案
我使用flutter_riverpod包实现搜索框时出现报错,相关代码如下:
UI 代码
TextFormField( … onChanged: (search) => controller.updateSearch(search), onSaved: (search) { search == null ? null : controller.updateSearch(search); }, ),
itemBuilder: (context, index) { if (mounted) { return name.contains(state.search) ? ListTile(title: Text(name)) : Container(); } return Container(); },
Controller 代码
class Controller extends StateNotifier<State> { Controller() : super(State()); void updateSearch(String search) => state = state.copyWith(search: search); } final controllerProvider = StateNotifierProvider.autoDispose< Controller, State>((ref) { return Controller(); });
State 代码
class State { State({this.search = "", this.value = const AsyncValue.data(null)}); final String search; final AsyncValue<void> value; bool get isLoading => value.isLoading; State copyWith({String? search, AsyncValue<void>? value}) { return State(search: search ?? this.search, value: value ?? this.value); } }
现有代码存在的问题
- State类未重写相等性判断:StateNotifier依赖不可变状态的
==和hashCode判断状态是否更新,现有State类未实现这两个方法,可能导致状态更新后UI不刷新,或触发不必要的重建。 - 列表构建逻辑不合理:在
itemBuilder中返回空Container会导致列表出现空白占位,应该先过滤数据再构建列表;mounted判断在此场景下无意义,反而可能阻碍状态更新后的UI重建。 - TextFormField的onSaved逻辑冗余:空值判断可简化,无需返回null。
基于Flutter Riverpod的正确实现
1. 修正State类(重命名为SearchState避免冲突)
class SearchState { SearchState({this.search = "", this.value = const AsyncValue.data(null)}); final String search; final AsyncValue<void> value; bool get isLoading => value.isLoading; SearchState copyWith({String? search, AsyncValue<void>? value}) { return SearchState( search: search ?? this.search, value: value ?? this.value, ); } @override bool operator ==(Object other) => identical(this, other) || other is SearchState && runtimeType == other.runtimeType && search == other.search && value == other.value; @override int get hashCode => search.hashCode ^ value.hashCode; }
2. 修正Controller类(重命名为SearchController)
class SearchController extends StateNotifier<SearchState> { SearchController() : super(SearchState()); void updateSearch(String search) => state = state.copyWith(search: search); } final searchControllerProvider = StateNotifierProvider.autoDispose<SearchController, SearchState>((ref) { return SearchController(); });
3. 优化UI实现
class SearchPage extends ConsumerWidget { const SearchPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final searchState = ref.watch(searchControllerProvider); final searchController = ref.read(searchControllerProvider.notifier); // 假设原始数据列表为names final List<String> names = ["Apple", "Banana", "Orange", "Grape"]; final filteredNames = names.where((name) => name.contains(searchState.search)).toList(); return Scaffold( appBar: AppBar(title: const Text("搜索")), body: Column( children: [ TextFormField( decoration: const InputDecoration(hintText: "输入搜索内容"), onChanged: searchController.updateSearch, onSaved: (search) { if (search != null) searchController.updateSearch(search); }, ), Expanded( child: ListView.builder( itemCount: filteredNames.length, itemBuilder: (context, index) { return ListTile(title: Text(filteredNames[index])); }, ), ), ], ), ); } }
报错原因推测
如果未重写State类的==和hashCode,StateNotifier会认为新生成的State与旧State相等,不会通知UI更新;另外,itemBuilder中返回空容器可能导致列表布局异常,引发渲染报错。
不依赖第三方包、不使用setState的实现方案
可以使用Flutter原生的ValueNotifier或ChangeNotifier实现:
方案1:使用ValueNotifier
class SearchPage extends StatefulWidget { const SearchPage({super.key}); @override State<SearchPage> createState() => _SearchPageState(); } class _SearchPageState extends State<SearchPage> { final ValueNotifier<String> _searchNotifier = ValueNotifier(""); final List<String> _names = ["Apple", "Banana", "Orange", "Grape"]; @override void dispose() { _searchNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("搜索")), body: Column( children: [ TextFormField( decoration: const InputDecoration(hintText: "输入搜索内容"), onChanged: (text) => _searchNotifier.value = text, ), Expanded( child: ValueListenableBuilder<String>( valueListenable: _searchNotifier, builder: (context, searchText, child) { final filteredNames = _names.where((name) => name.contains(searchText)).toList(); return ListView.builder( itemCount: filteredNames.length, itemBuilder: (context, index) { return ListTile(title: Text(filteredNames[index])); }, ); }, ), ), ], ), ); } }
方案2:使用ChangeNotifier
class SearchModel extends ChangeNotifier { String _search = ""; String get search => _search; void updateSearch(String text) { _search = text; notifyListeners(); } } class SearchPage extends StatefulWidget { const SearchPage({super.key}); @override State<SearchPage> createState() => _SearchPageState(); } class _SearchPageState extends State<SearchPage> { final SearchModel _searchModel = SearchModel(); final List<String> _names = ["Apple", "Banana", "Orange", "Grape"]; @override void dispose() { _searchModel.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("搜索")), body: Column( children: [ TextFormField( decoration: const InputDecoration(hintText: "输入搜索内容"), onChanged: _searchModel.updateSearch, ), Expanded( child: AnimatedBuilder( animation: _searchModel, builder: (context, child) { final filteredNames = _names.where((name) => name.contains(_searchModel.search)).toList(); return ListView.builder( itemCount: filteredNames.length, itemBuilder: (context, index) { return ListTile(title: Text(filteredNames[index])); }, ); }, ), ), ], ), ); } }
内容的提问来源于stack exchange,提问作者My Car
相关产品推荐
相关产品推荐

