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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 05:35:25