Search code examples
flutterriverpodflutter-isar

How to filter with a query group using isar link


I am creating an app for recording and searching restaurants with Flutter. I am using Isar for local DB and Riverpod for state management.

There are two collections: restaurants and locations. enter image description here

Restaurant model:

@collection
class Restaurant {
  Id? id;

  @Index(unique: true)
  late String name;

  final location = IsarLink<Location>();

  Restaurant({
    required this.name,
  });
}

Location model:

@collection
class Location {
  Id? id;

  @Index(unique: true)
  late String name;

  @Backlink(to: 'location')
  final restaurants = IsarLinks<Restaurant>();

  Location({
    required this.name,
  });
}

My goal is to implement a search feature that can filter restaurants based on the query string (restaurant name) and locations. I plan to use QueryGroup because I have to implement other filter values and manage its state later even though it is simplified for this question.

QueryGroup class:

class QueryGroup {
  final String queryString;
  final List<Location> filterLocations;

  QueryGroup({
    required this.queryString,
    required this.filterLocations,
  });
}

Now I want to implement a restaurant notifier to manage the state of Future<List<Restaurant>> to show a filtered restaurant list.

final isar = IsarHelper().isar;

@riverpod
class RestaurantsNotifier extends _$RestaurantsNotifier {
  @override
  Future<List<Restaurant>> build() async {
    List<Restaurant> restaurants = await _fetchAllRestaurants();
    return restaurants;
  }

  Future<List<Restaurant>> _fetchAllRestaurants() async {
    List<Restaurant> restaurants = await isar.restaurants.where().findAll();
    await _loadIsarLinksForRestaurants(restaurants);
    state = AsyncValue.data(restaurants);
    return restaurants;
  }

  void searchRestaurantsWithQueryGroup({required QueryGroup queryGroup}) async {
    final List<Restaurant> filteredRestaurants = await isar.restaurants
        .filter()
        .nameContains(queryGroup.queryString, caseSensitive: false)
        .and()
        .location(
          (q) => q.nameContains(
            queryGroup.filterLocations[0].name, // I want to make this iteration of locations
            caseSensitive: false,
          ),
        )
        .findAll();

    state = AsyncValue.data(filteredRestaurants);
  }
}

If a user's input is like this;

queryString: French
Locations: [Location(name: 'Shibuya'), Location(name: 'Tokyo')]

Then the search should be French for the restaurant name AND (Shibuya OR Tokyo for the locations). I referred to the Isar doc (https://isar.dev/queries.html) but I don't get how to achieve this. How can I modify the searchRestaurantsWithQueryGroup method?


Solution

  • Looks like isar provides an anyOf method that might be what you're looking for. This isn't tested/compiled, but your query could look something like this:

          final List<Restaurant> filteredRestaurants = await isar.restaurants
            .filter()
            .nameContains(queryGroup.queryString, caseSensitive: false)
            .and
            .anyOf(
              queryGroup.filterLocations,
              (q, location) => q.nameContains(location.name, caseSensitive: false,)
            ).findAll();
    

    doc: https://isar.dev/queries.html#filters