Advanced Usage

This section provides an overview of some advanced API features.

Update Query with Controller

CozyQueryController

The CozyQueryController exposes an API that helps you apply predicates to your query and modify the returned data. This controller allows you to filter, join, order, and customize your queries efficiently.

Example Usage

final controller = CozyQueryController<MyModel>();
await controller.addWhere([PredicateGroup(predicates: [Predicate.equals('field', 'value')])]);
await controller.addJoin([Join(table: 'other_table', condition: 'other_table.id = my_table.other_id')]);
await controller.orderBy([OrderBy(field: 'created_at', direction: OrderDirection.desc)]);
await controller.addCustomQuery(CozyQueryBuilder<MyModel>());
await controller.reset();

Practical Example

Here is a practical example of how to use CozyQueryController to filter recipes based on user input in a search bar:

class RecipeListView extends StatefulWidget {
    
    _RecipeListViewState createState() => _RecipeListViewState();
}

class _RecipeListViewState extends State<RecipeListView> {
static final CozyQueryController<Recipe> _queryController = CozyQueryController<Recipe>();
final CozyQueryListener<Recipe> _recipeQuery = CozyData.queryListener<Recipe>(controller: _queryController);

final TextEditingController _searchController = TextEditingController();


/// This method filters the data to retrieve all recipes where the name or any ingredient contains the value from the [_searchController].
/// It uses the `CozyQueryController` to apply a predicate that checks for the presence of the search term in either the recipe name or the ingredients list.
/// The predicate is structured to use an "OR" condition to ensure that either field matching the search term will return the recipe.
    void _updateQuery() async {
      await  _queryController.addWhere([
            PredicateGroup(
                predicates: [
                    Predicate.contains("name", _searchController.text),
                ],
                type: PredicateGroupType.or,
                subgroups: [
                    PredicateGroup(
                        predicates: [
                            Predicate.contains("ingredients", _searchController.text),
                        ],
                    ),
                ],
            )
        ]);
    }

    
    void initState() {
        super.initState();
        /// Listen to the search and then filter the data based on the search
        _searchController.addListener(_updateQuery);
    }

    
    Widget build(BuildContext context) {
        return Scaffold(
            body: CustomScrollView(
                slivers: [
                    _buildSearchBar(),
                    _buildRecipeList(),
                ],
            ),
        );
    }

    Widget _buildSearchBar() {
        return SliverToBoxAdapter(
            child: Padding(
                padding: const EdgeInsets.all(13),
                child: CupertinoSearchTextField(
                    placeholder: 'Search for recipes or ingredients',
                    backgroundColor: const Color.fromRGBO(227, 227, 233, 1),
                    controller: _searchController,
                ),
            ),
        );
    }

    Widget _buildRecipeList() {
        return SliverToBoxAdapter(
            child: ListenableBuilder(
                listenable: _recipeQuery,
                builder: (context, _) {
                    final recipes = _recipeQuery.items;
                    if (recipes.isEmpty) {
                        return const Center(
                            child: Text(
                                "Please click the + button to add a new recipe\n\nOnce added, you can edit it by tapping on it",
                                textAlign: TextAlign.center,
                            ),
                        );
                    }
                    return Container(
                        margin: const EdgeInsets.symmetric(horizontal: 18),
                        decoration: BoxDecoration(
                            color: Colors.white,
                            borderRadius: BorderRadius.circular(13),
                        ),
                        child: SingleChildScrollView(
                            child: Column(
                                children: List.generate(
                                    recipes.length,
                                    (index) {
                                        final recipe = recipes[index];
                                        final isLastItem = index == recipes.length - 1;
                                        return Column(
                                            children: [
                                                ListTile(
                                                    minVerticalPadding: 45,
                                                    title: Text(
                                                        recipe.name,
                                                        style: const TextStyle(
                                                            fontSize: 16,
                                                            fontWeight: FontWeight.w500,
                                                        ),
                                                    ),
                                                    trailing: const Icon(
                                                        Icons.arrow_forward_ios,
                                                        color: Colors.grey,
                                                        size: 15,
                                                    )
                                                ),
                                                if (!isLastItem)
                                                    Divider(
                                                        height: 1,
                                                        indent: 15,
                                                        color: Colors.grey.shade200,
                                                    ),
                                            ],
                                        );
                                    },
                                ),
                            ),
                        ),
                    );
                },
            ),
        );
    }

    
    void dispose() {
        _recipeQuery.dispose();
        _searchController.dispose();
        super.dispose();
    }

}

In this example, the CozyQueryController is used to filter recipes based on the user's input in a search bar. The _updateQuery method applies a predicate to filter recipes where the name or ingredients contain the search term. The ListenableBuilder widget listens to changes in the query and updates the UI accordingly.