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

Flutter中如何结合Hits Searcher与无限滚动实现Algolia多索引搜索

实现Algolia多索引搜索结合Flutter Hits Searcher与无限滚动分页

核心思路

用MultiSearcher管理两个索引的搜索实例,同步它们的查询参数(关键词、页码),合并两个索引的搜索结果后传递给PagingController实现无限滚动。如果两个索引存在重复用户,可基于uid字段去重。

修改后的完整代码

import 'dart:async';
import 'package:algolia_helper_flutter/algolia_helper_flutter.dart';
import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

// 假设你的AlgoliaUser模型已定义
class AlgoliaUser {
  final String uid;
  final String username;
  // 其他字段...

  AlgoliaUser.fromJson(Map<String, dynamic> json)
      : uid = json['uid'],
        username = json['username'];
}

class UserSearchScreen extends StatefulWidget {
  const UserSearchScreen({super.key});

  @override
  State<UserSearchScreen> createState() => _UserSearchScreenState();
}

class _UserSearchScreenState extends State<UserSearchScreen> {
  final TextEditingController _searchTextEditingController = TextEditingController();
  Timer? _debounce;

  // 使用MultiSearcher管理多索引搜索
  final MultiSearcher _multiSearcher = MultiSearcher(
    applicationID: 'myAppID',
    apiKey: 'myApiKey',
  );

  // 初始化两个索引的搜索器
  late final HitsSearcher _usersSearcher = _multiSearcher.addHitsSearcher(
    indexName: 'users',
  );
  late final HitsSearcher _anotherUsersSearcher = _multiSearcher.addHitsSearcher(
    indexName: 'another_users', // 替换为你的第二个索引名称
  );

  final PagingController<int, AlgoliaUser> _pagingController =
      PagingController(firstPageKey: 0);

  // 合并两个索引的搜索结果流,处理分页和去重
  Stream<HitsPage<AlgoliaUser>> get _combinedSearchPage {
    return CombineLatestStream.combine2(
      _usersSearcher.responses,
      _anotherUsersSearcher.responses,
      (Response usersResp, Response anotherUsersResp) {
        // 转换为用户模型
        final users = usersResp.hits.map((hit) => AlgoliaUser.fromJson(hit)).toList();
        final anotherUsers = anotherUsersResp.hits.map((hit) => AlgoliaUser.fromJson(hit)).toList();

        // 合并并去重(基于uid)
        final combined = <String, AlgoliaUser>{};
        for (var user in [...users, ...anotherUsers]) {
          combined[user.uid] = user;
        }
        final uniqueUsers = combined.values.toList();

        // 计算下一页页码:任意一个索引还有结果则返回下一页
        final hasMoreUsers = usersResp.nbPages > usersResp.page + 1;
        final hasMoreAnotherUsers = anotherUsersResp.nbPages > anotherUsersResp.page + 1;
        final actualNextPageKey = hasMoreUsers || hasMoreAnotherUsers ? usersResp.page + 1 : null;

        return HitsPage(
          items: uniqueUsers,
          pageKey: usersResp.page,
          nextPageKey: actualNextPageKey,
        );
      },
    );
  }

  @override
  void initState() {
    super.initState();

    // 监听合并后的搜索结果流,更新分页控制器
    _combinedSearchPage.listen((page) {
      if (page.pageKey == 0) {
        _pagingController.refresh();
      }
      _pagingController.appendPage(page.items, page.nextPageKey);
    }).onError((error) => _pagingController.error = error);

    // 监听分页请求,同步更新两个索引的页码
    _pagingController.addPageRequestListener((pageKey) {
      _usersSearcher.applyState((state) => state.copyWith(page: pageKey));
      _anotherUsersSearcher.applyState((state) => state.copyWith(page: pageKey));
    });
  }

  @override
  void dispose() {
    _multiSearcher.dispose();
    _pagingController.dispose();
    _searchTextEditingController.dispose();
    _debounce?.cancel();
    super.dispose();
  }

  void _onSearchChanged(String query) {
    if (_debounce?.isActive ?? false) {
      _debounce?.cancel();
    }
    _debounce = Timer(const Duration(milliseconds: 500), () {
      // 同步更新两个索引的查询关键词和重置页码
      _usersSearcher.applyState((state) => state.copyWith(
            query: query,
            page: 0,
          ));
      _anotherUsersSearcher.applyState((state) => state.copyWith(
            query: query,
            page: 0,
          ));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TextField(
          controller: _searchTextEditingController,
          onChanged: _onSearchChanged,
          decoration: const InputDecoration(
            hintText: '搜索用户...',
            border: InputBorder.none,
          ),
        ),
      ),
      body: PagedListView<int, AlgoliaUser>(
        pagingController: _pagingController,
        builderDelegate: PagedChildBuilderDelegate<AlgoliaUser>(
          itemBuilder: (context, item, index) {
            return ListTile(
              title: Text(item.username),
              subtitle: Text(item.uid),
            );
          },
          firstPageErrorIndicatorBuilder: (context) => const Text('加载失败'),
          noItemsFoundIndicatorBuilder: (context) => const Text('没有找到用户'),
        ),
      ),
    );
  }
}

// 保持你原有的HitsPage和SearchMetadata类(如果需要)
class HitsPage<T> {
  final List<T> items;
  final int pageKey;
  final int? nextPageKey;

  HitsPage({
    required this.items,
    required this.pageKey,
    required this.nextPageKey,
  });
}

class SearchMetadata {
  SearchMetadata.fromResponse(Response response);
}

关键说明

  1. MultiSearcher管理多索引:替代单索引的HitsSearcher,统一管理多个索引的搜索实例,确保API调用的一致性。
  2. 结果合并与去重:通过CombineLatestStream合并两个索引的响应流,利用uid字段去重避免重复用户展示。
  3. 同步分页与查询:分页请求时同步更新两个索引的页码;搜索关键词变化时,同时重置两个索引的查询参数。
  4. 分页逻辑控制:只有当任意一个索引还有未加载的结果时,才返回下一页页码,避免无效请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:50:22