如何用Flutter Future Provider实现API后端实时搜索功能
问题描述
我需要实现一个基于API的后端搜索功能,搜索逻辑由后端处理,要求用户在TextField输入时,实时从后端获取对应的数据。目前用Future Provider来管理数据获取,想知道怎么实现这个需求。附上我的代码示例,预期UI效果为搜索框下方实时展示搜索建议列表。
现有代码
SearchPage
class SearchPage extends StatelessWidget { const SearchPage({super.key}); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => SearchProvider()), ], child: Consumer<SearchProvider>( builder: (context, value, child) => Scaffold( appBar: BuildSearchAppBar(), body: Body() ), ), ); } }
BuildSearchAppBar
class BuildSearchAppBar extends StatelessWidget with PreferredSizeWidget { BuildSearchAppBar({super.key}); @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); @override Widget build(BuildContext context) { SearchProvider provider = Provider.of<SearchProvider>(context); return AppBar( title: TextField( controller: provider.textEditingController, decoration: InputDecoration( alignLabelWithHint: true, floatingLabelBehavior: FloatingLabelBehavior.never, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6.r), ), constraints: BoxConstraints.tight(Size(1.sw, 40.h)), labelText: "Search", prefixIcon: Icon( Icons.search, size: 20.r, ), labelStyle: TextStyle(fontSize: 15.sp, fontStyle: FontStyle.italic), ), onChanged: (value) { provider.showProductSuggetion(); }, ), actions: [ IconButton( onPressed: () { provider.textEditingController.clear(); }, icon: const Icon(Icons.clear), ), ], ); } }
SearchProvider
class SearchProvider with ChangeNotifier { bool isClicked = false; final TextEditingController textEditingController = TextEditingController(); void showProductSuggetion() { isClicked = true; FutureProvider( create: (_) => searchSuggestionService(textEditingController.text), initialData: SearchSuggetionModel()); notifyListeners(); } Future<SearchSuggetionModel> searchSuggestionService(String keyword) async { Map<String, Map<String, Object>> singleProductVariable = { "productPrams": {"search": keyword, "visibility": true, "approved": true} }; QueryResult queryResult = await qlclient.query( QueryOptions( document: gql(QueryDocument.searchSuggestion), variables: singleProductVariable), ); var data = queryResult.data as Map<String, dynamic>; print(data); var body = SearchSuggetionModel.fromJson(data); notifyListeners(); return body; } }
解决方案
你的代码里有几个关键问题得调整,才能实现实时搜索的效果:
1. 别在Provider方法里创建FutureProvider
FutureProvider是Widget,得放到Widget树里才能正常管理异步数据的生命周期,不能在SearchProvider的方法里直接实例化它。
2. 加防抖逻辑,避免疯狂发请求
用户每输入一个字符就触发一次请求太浪费资源,加个防抖(延迟几百毫秒再发请求),等用户输入停了再查,减少服务器压力。
改完后的完整代码
1. 重构SearchProvider
import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:graphql/client.dart'; class SearchProvider with ChangeNotifier { bool isLoading = false; final TextEditingController textEditingController = TextEditingController(); Timer? _debounceTimer; SearchSuggetionModel? _searchResults; SearchSuggetionModel? get searchResults => _searchResults; // 处理输入变化,带防抖 void onSearchTextChanged(String keyword) { isLoading = true; notifyListeners(); // 取消之前的延迟请求 _debounceTimer?.cancel(); // 延迟300ms再发请求,避免频繁触发 _debounceTimer = Timer(const Duration(milliseconds: 300), () { _fetchSearchData(keyword); }); } // 实际发起API请求 Future<void> _fetchSearchData(String keyword) async { try { var variables = { "productPrams": {"search": keyword, "visibility": true, "approved": true} }; var result = await qlclient.query( QueryOptions( document: gql(QueryDocument.searchSuggestion), variables: variables, ), ); if (!result.hasException) { _searchResults = SearchSuggetionModel.fromJson(result.data!); } else { _searchResults = null; // 请求失败清空结果 } } catch (e) { _searchResults = null; if (kDebugMode) print('搜索出错: $e'); } finally { isLoading = false; notifyListeners(); } } // 清空搜索内容 void clearSearch() { textEditingController.clear(); _searchResults = null; _debounceTimer?.cancel(); isLoading = false; notifyListeners(); } @override void dispose() { textEditingController.dispose(); _debounceTimer?.cancel(); super.dispose(); } }
2. 调整BuildSearchAppBar
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class BuildSearchAppBar extends StatelessWidget with PreferredSizeWidget { const BuildSearchAppBar({super.key}); @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); @override Widget build(BuildContext context) { final searchProvider = Provider.of<SearchProvider>(context); return AppBar( title: TextField( controller: searchProvider.textEditingController, decoration: InputDecoration( alignLabelWithHint: true, floatingLabelBehavior: FloatingLabelBehavior.never, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6.r), ), constraints: BoxConstraints.tight(Size(1.sw, 40.h)), labelText: "搜索", prefixIcon: Icon( Icons.search, size: 20.r, ), labelStyle: TextStyle(fontSize: 15.sp, fontStyle: FontStyle.italic), ), onChanged: searchProvider.onSearchTextChanged, ), actions: [ IconButton( onPressed: searchProvider.clearSearch, icon: const Icon(Icons.clear), ), ], ); } }
3. 重构SearchPage和Body组件
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class SearchPage extends StatelessWidget { const SearchPage({super.key}); @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => SearchProvider(), child: const Scaffold( appBar: BuildSearchAppBar(), body: SearchBody(), ), ); } } class SearchBody extends StatelessWidget { const SearchBody({super.key}); @override Widget build(BuildContext context) { final searchProvider = Provider.of<SearchProvider>(context); // 加载中显示转圈 if (searchProvider.isLoading) { return const Center(child: CircularProgressIndicator()); } final results = searchProvider.searchResults; // 无结果提示 if (results == null || results.data.isEmpty) { return const Center(child: Text("暂无搜索结果")); } // 渲染搜索结果列表 return ListView.builder( itemCount: results.data.length, itemBuilder: (context, index) { final item = results.data[index]; return ListTile( title: Text(item.title), // 根据你的数据模型添加更多内容,比如副标题、图片等 ); }, ); } }
重点改进说明
- 防抖处理:用
Timer延迟请求,避免用户输入过程中疯狂发API请求,既省资源又减少服务器压力。 - 状态管理简化:直接在
SearchProvider里管理加载状态、搜索结果,不用额外套FutureProvider,逻辑更清晰。 - 错误处理:捕获请求异常,避免崩溃,同时更新状态提示用户。
- 资源清理:在dispose里释放控制器和定时器,防止内存泄漏。
如果非要用FutureProvider,也可以把它嵌套在SearchBody里,监听关键词变化来创建Future:
class SearchBody extends StatelessWidget { const SearchBody({super.key}); @override Widget build(BuildContext context) { final searchProvider = Provider.of<SearchProvider>(context); final keyword = searchProvider.textEditingController.text; return FutureProvider<SearchSuggetionModel?>( create: (_) => searchProvider.searchSuggestionService(keyword), initialData: null, child: Consumer<SearchSuggetionModel?>( builder: (context, results, child) { if (searchProvider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (results == null || results.data.isEmpty) { return const Center(child: Text("暂无搜索结果")); } return ListView.builder( itemCount: results.data.length, itemBuilder: (context, index) { final item = results.data[index]; return ListTile(title: Text(item.title)); }, ); }, ), ); } }
这种方式得把searchSuggestionService改成public方法,同时还是要加防抖逻辑来控制请求频率。
内容的提问来源于stack exchange,提问作者Lujo Morvic
相关产品推荐
相关产品推荐

