【问题标题】:Flutter searching using typeahead使用 typeahead 进行颤振搜索
【发布时间】:2021-08-20 01:42:38
【问题描述】:

我有大量的 json 数据,我正在从资产中加载这些数据。我需要使用 recipeName 从食谱中进行搜索,并且我正在使用 flutter typeahead 创建搜索栏和功能。

应用启动时,我正在加载所有配方对象。下面的代码基本上返回了所有的菜谱。

  List<RecipeModel> getAllRecipes() {
    for (int i = 0; i < _appData.recipeCategories!.length; i++) {
      for (int j = 0; j < _appData.recipeCategories![i].recipes!.length; j++) {
        _recipeList.add(_appData.recipeCategories![i].recipes![j]);
      }
    }
    return _recipeList;
  }

这段代码负责搜索(Search.dart):

class Search {
  List<RecipeModel> _recipeList = Store.instance.getAllRecipes(); // Store loads data during splash screen
  late RecipeModel recipe;
  RecipeModel returnRecipe(String? suggestion) {
    for (int i = 0; i < _recipeList.length; i++) {
      if (suggestion == _recipeList[i].recipeName) {
        return _recipeList[i];
      }
    }

    return recipe;
  }
}

现在是预输入部分。 Flutter typeahead 需要 4 个参数 textFieldConfigurationsuggestionsCallbackitemBuilderonSuggestionSelected

对于SuggestionsCallback,当屏幕加载并从该列表中搜索时,我将所有食谱名称加载到列表中:

  Future<List<String>> getSuggestions(String str) async {
    return List.of(recipeNamesList).where(
      (recipe) {
        final recipeLower = recipe.toLowerCase();
        final queryLower = str.toLowerCase();

        return recipeLower.contains(queryLower);
      },
    ).toList();
  }

这是整个 typeahead 小部件:

  Widget buildSearchTextInput() {
    return Container(
      padding: EdgeInsets.all(10),
      child: TypeAheadFormField<String?>(
        textFieldConfiguration: TextFieldConfiguration(
          controller: _textEditingController,
          focusNode: _focusNode,
          autofocus: true,
          decoration: InputDecoration(
            hintText: "Enter recipe name",
          ),
        ),
        suggestionsCallback: (query) async {
          print("query: $query");
          if (query.length == 0) return [];
          return await getSuggestions(query);
        },
        hideOnEmpty: true,
        itemBuilder: (context, String? suggestion) =>
            SearchResultCard(suggestion: suggestion),
        onSuggestionSelected: (String? suggestion) {
          openRecipeDetailsPage(
            context,
            Search().returnRecipe(suggestion),
          );
        },
      ),
    );
  }

如您所见,我正在使用上述类进行临时搜索。现在对于openSuggestionsSelected,我必须将用户发送到recipeDetailsScreen,它接受RecipeModel 的对象。

  1. 我一直在想,虽然我要进行临时搜索,但我可以在一定程度上减少混乱吗?我的意思是除了加载商店中所有食谱的功能外,我还有两个列表可以工作。一个带有食谱的名称,一个带有所有食谱。有没有办法使用一个列表来做到这一点?

  2. 我可以提高搜索效率吗?我不认为使用两个列表是一个好主意。

N.B:我编写的解决方案有效。我只需要一些可以消除混乱并希望降低搜索时间复杂度或至少提高功能效率的东西。

【问题讨论】:

  • Hi 实际上是在寻找一些非常简单的东西来实现负责返回对象的搜索功能。我想实现 trie 会花费很多时间?
  • 不用实现,已经有a package that does that for you了。它甚至针对自动完成任务进行了优化。
  • 您熟悉预输入吗?我需要返回一个对象。看了包我觉得不行吗?
  • 您阅读过trie 文档吗?只需使用getAllWordsWithPrefix 方法将query 传递给它并在suggestionsCallback 中返回它,例如trie.getAllWordsWithPrefix(query).

标签: flutter dart


【解决方案1】:

您可以通过两种方式降低代码的复杂性:

  1. 在搜索部分使用Map&lt;String, RecipeModel&gt;
class Search {
  // This will still be O(n)
  Map<String, RecipeModel> _map = Map.fromIterable(
    Store.instance.getAllRecipes(),
    key: (recipe) => recipe.name);

  late RecipeModel recipe;

  RecipeModel returnRecipe(String? suggestion) {
    if (suggestion == null) return recipe;
    // This will be O(1) instead of O(n) [better]
    final RecipeModel? found = _map[suggestion];
    return found ?? recipe;
  }
}
  1. 在预输入部分使用 Trie(这里我使用 trie,但您可以改用 autotrie):
 class Search {
  final Map<String, RecipeModel> _map = Map.fromIterable(
    Store.instance.getAllRecipes(),
    key: (recipe) => recipe.name);

  final late Trie trie;

  Search() {
    // This will be O(n)
    trie = Trie.list(map.keys().toList());
  }

  // ...

  List<String> returnSuggestions(String prefix) {
    // This will be O(W*L) instead of O(n^2) [better]
    return trie.getAllWordsWithPrefix(prefix);
  }
}

更改您的代码:

final Search search = Search();

// ...

Widget buildSearchTextInput() {
  return Container(
    padding: EdgeInsets.all(10),
    child: TypeAheadFormField<String?>(
      // ...
      suggestionsCallback: (query) async {
        if (query.isEmpty) return [];
        return search.returnSuggestions(query);
      },
      // ...
      onSuggestionSelected: (String? suggestion) {
        openRecipeDetailsPage(context, search.returnRecipe(suggestion));
      },
    ),
  );
}

您仍然会有两个“列表”,但总体上您的复杂性会变得更好。

【讨论】:

  • 好吧,我明白了。我太专注于两个列表(一个用于 recipeNames,一个用于配方对象)的想法,我完全监督了这个简单的事情。
  • 解决方案有效,除了空安全问题。我想我会迁移到 autotrie,因为它的安全性为零
猜你喜欢
  • 1970-01-01
  • 2021-10-22
  • 2019-05-25
  • 1970-01-01
  • 2020-08-03
  • 2021-06-12
  • 2021-07-11
  • 2023-03-12
  • 1970-01-01
相关资源
最近更新 更多