How to filter a listview with buttons?

Viewed 177

I'm trying to develop a restaurant menu app and i need that when i click one of the text buttons bellow it filters the items according to which button i tap. when i built the "meal" object, i created an atribute called "idDiaSem", and i have 5 diferent id's that i need to display 5 diferent lists of itens based on the ID i choose. Here's the concept:

display of the menu

Expanded(
            child: ListView.builder(
              itemCount: dayOfWeekMeals.length,
              itemBuilder: (ctx, index) {
                return Card(
                  elevation: 0,
                  margin: (index == dayOfWeekMeals.length - 1)
                      ? const EdgeInsets.only(bottom: 0, left: 20, right: 20)
                      : const EdgeInsets.only(left: 20, right: 20),
                  child: ClipRRect(
                    borderRadius: (index == 0)
                        ? const BorderRadius.only(
                            topLeft: Radius.circular(50),
                            topRight: Radius.circular(50),
                          )
                        : (index == dayOfWeekMeals.length - 1)
                            ? const BorderRadius.only(
                                bottomLeft: Radius.circular(50),
                                bottomRight: Radius.circular(50),
                              )
                            : BorderRadius.circular(0),
                    child: MealItem(dayOfWeekMeals[index]),
                  ),
                );
              },
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              GestureDetector(
                onTap: () {},
                child: Text(
                  'Segunda \n____________',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                      fontSize: 11,
                      color: Theme.of(context).colorScheme.primary,
                      fontWeight: FontWeight.bold),
                ),
              ),
              GestureDetector(
                onTap: () {},
                child: const Text(
                  'Terça \n____________',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 11),
                ),
              ),
              GestureDetector(
                onTap: () {},
                child: const Text(
                  'Quarta \n____________',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 11),
                ),
              ),
              GestureDetector(
                onTap: () {},
                child: const Text(
                  'Quinta \n____________',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 11),
                ),
              ),
              GestureDetector(
                onTap: () {},
                child: const Text(
                  'Sexta \n____________',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 11),
                ),
              ),
            ],
          ),
        ],
      ),
class Meal {
  final String id;
  final String descricao;
  final List<String> ingredients;
  final List<String> idDiaSem;
  final String imageUrl;

  const Meal({
    required this.id,
    required this.descricao,
    required this.ingredients,
    required this.idDiaSem,
    required this.imageUrl,
  });
}

Still i can`t figure out how to create the filter, can anyone help me please? i need the button to change the filter when it's pressed

2 Answers

Filter it with List.where(predicate) in the list of meals and the predicate checks if any of the weekdays match the idDiaSem. And then assign it to the dayOfWeekMeals variable.

It's going to be like the below snippet:

final dayOfWeekMeals = meals
    .where((meal) => meal.idDiaSem.any((idDiaSem) => idDiaSem == _idDiaSem))
    .toList();

Here's the result:

Screenshot

Here's a complete minimal-reproducible-example:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _idDiaSem = 'seg';

  @override
  Widget build(BuildContext context) {
    final dayOfWeekMeals = meals
        .where((meal) => meal.idDiaSem.any((idDiaSem) => idDiaSem == _idDiaSem))
        .toList();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Gastronomia'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: dayOfWeekMeals.length,
              itemBuilder: (ctx, index) {
                return Card(
                  elevation: 0,
                  margin: (index == dayOfWeekMeals.length - 1)
                      ? const EdgeInsets.only(bottom: 0, left: 20, right: 20)
                      : const EdgeInsets.only(left: 20, right: 20),
                  child: ClipRRect(
                    borderRadius: (index == 0)
                        ? const BorderRadius.only(
                            topLeft: Radius.circular(50),
                            topRight: Radius.circular(50),
                          )
                        : (index == dayOfWeekMeals.length - 1)
                            ? const BorderRadius.only(
                                bottomLeft: Radius.circular(50),
                                bottomRight: Radius.circular(50),
                              )
                            : BorderRadius.circular(0),
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Text(dayOfWeekMeals[index].descricao),
                    ),
                  ),
                );
              },
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              SemanaButton(
                'Segunda',
                onPressed: () => setState(() => _idDiaSem = 'seg'),
                selected: _idDiaSem == 'seg',
              ),
              SemanaButton(
                'Terça',
                onPressed: () => setState(() => _idDiaSem = 'ter'),
                selected: _idDiaSem == 'ter',
              ),
              SemanaButton(
                'Quarta',
                onPressed: () => setState(() => _idDiaSem = 'qua'),
                selected: _idDiaSem == 'qua',
              ),
              SemanaButton(
                'Quinta',
                onPressed: () => setState(() => _idDiaSem = 'qui'),
                selected: _idDiaSem == 'qui',
              ),
              SemanaButton(
                'Sexta',
                onPressed: () => setState(() => _idDiaSem = 'sex'),
                selected: _idDiaSem == 'sex',
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class SemanaButton extends StatelessWidget {
  final String text;
  final bool? selected;
  final VoidCallback onPressed;
  const SemanaButton(
    this.text, {
    Key? key,
    required this.onPressed,
    this.selected,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onPressed,
      child: Text(
        '$text \n____________',
        textAlign: TextAlign.center,
        style: TextStyle(
          fontSize: 11,
          color:
              selected == true ? Theme.of(context).colorScheme.primary : null,
          fontWeight: selected == true ? FontWeight.bold : null,
        ),
      ),
    );
  }
}

class Meal {
  final String id;
  final String descricao;
  final List<String> ingredients;
  final List<String> idDiaSem;
  final String imageUrl;

  const Meal({
    required this.id,
    required this.descricao,
    required this.ingredients,
    required this.idDiaSem,
    required this.imageUrl,
  });
}

var id = 0;

final meals = [
  Meal(
    id: '${++id}',
    descricao: 'Feijão Tropeiro',
    ingredients: [],
    idDiaSem: ['seg'],
    imageUrl: '',
  ),
  Meal(
    id: '${++id}',
    descricao: 'Feijoada',
    ingredients: [],
    idDiaSem: ['sex'],
    imageUrl: '',
  ),
  Meal(
    id: '${++id}',
    descricao: 'Batata Doce Caramelada',
    ingredients: [],
    idDiaSem: ['seg'],
    imageUrl: '',
  ),
  Meal(
    id: '${++id}',
    descricao: 'Cubos Suínos ao Molho Escuro',
    ingredients: [],
    idDiaSem: ['seg'],
    imageUrl: '',
  ),
  Meal(
    id: '${++id}',
    descricao: 'Enrolado de Salsicha',
    ingredients: [],
    idDiaSem: ['seg'],
    imageUrl: '',
  ),
  Meal(
    id: '${++id}',
    descricao: 'Doce e Fruta',
    ingredients: [],
    idDiaSem: ['seg', 'ter', 'qua', 'qui', 'sex'],
    imageUrl: '',
  ),
  Meal(
    id: '${++id}',
    descricao: 'Buffet de Saladas',
    ingredients: [],
    idDiaSem: ['seg', 'ter', 'qua', 'qui', 'sex'],
    imageUrl: '',
  ),
];

Well, here is the recipe for the required logic:

  1. Define a variable to contain the id that you would to filter the meals according to.
int _filterId;
  1. When any filter button is pressed, just set the value of the _filterId inside a setState() block:
onTap: () {
    setState(() => _filterId = 0); // Take care of the ID
},
  1. At the begining of the build() method, Define a List of Meal that would contain the filtered meals in the future and set it to the dayOfWeekMeals list if the _filterId is null and apply the filter if it is not null, like the following:
List<Meal> _filteredMeals = _filterId == null ? dayOfWeekMeals : dayOfWeekMeals.where((meal) => meal.idDiaSem.contains(_filterId).toList();
  1. Use the _filteredMeals list in the ListView instead of dayOfWeekMeals, for example:
itemCount: _filteredMeals.length,
MealItem(_filteredMeals[index])

NOTE: Don't forget to replace the others.


Side Note: You can start with an initial filter by just starting the variable _filterId with an initial id of the filter.

Related