Home > Mobile >  Flutter search and map nested list of objects
Flutter search and map nested list of objects

Time:08-29

I am building a bottle delivery app using Flutter. I am having issues searching and mapping from my List of bottle categories. I am failing to search for any products in the search bar. I know the problem has to do with the fact that the list I am searching from is a different list from the one that was used to map values into the widget. I have been struggling for a while with this problem. How do I ensure I am searching and mapping from the same list? Can anyone please kindly assist. Here's a image of the app in web

Before Search enter image description here After Search (As you can see nothing appears when search happens) enter image description here Here's my code:

class Bottle {
  String id;
  String bottleName;
  String image;
  String bottletype;
  int time;
  double price;
  Bottle(
      {required this.id,
      required this.bottleName,
      required this.image,
      required this.bottletype,
      required this.time,
      required this.price});
}

class BottleCategory {
  int categoryNo;
  String bottleType;
  List<Bottle> bottleList;
  BottleCategory(
      {required this.categoryNo,
      required this.bottleType,
      required this.bottleList});
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
  ProductProvider productProvider = ProductProvider();
  late TabController tabController;
  late Bottle bottleProvider;

  final List<BottleCategory> categories = [
    BottleCategory(categoryNo: 1, bottleType: 'Brandy', bottleList: [
      Bottle(
          id: 'KLPB',
          bottleName: 'Klipdrift Brandy',
          image: 'lib/assets/images/KlipdriftBrandy.png',
          time: 20,
          price: 274.00,
          bottletype: 'Brandy'),
      Bottle(
          id: 'KWV3',
          bottleName: 'Kwv Brandy 3 years',
          image: 'lib/assets/images/KwvBrandy3years.jpg',
          time: 20,
          price: 274.00,
          bottletype: 'Brandy'),
    ]),
    BottleCategory(categoryNo: 2, bottleType: 'Gin', bottleList: [
      Bottle(
          id: 'BMBS',
          bottleName: 'Bombay Sapphire Gin',
          image: 'lib/assets/images/BombaySapphireGin.png',
          time: 20,
          price: 274.00,
          bottletype: 'Gin'),
      Bottle(
          id: 'TNGN',
          bottleName: 'Tanqueray Gin',
          image: 'lib/assets/images/TanquerayGin.png',
          time: 20,
          price: 274.00,
          bottletype: 'Gin'),
    ]),
    BottleCategory(categoryNo: 3, bottleType: 'Soft Drinks', bottleList: [
      Bottle(
          id: 'COCA',
          bottleName: 'Coca Cola',
          image: 'lib/assets/images/CocaCola.jpg',
          time: 20,
          price: 274.00,
          bottletype: 'Soft Drinks'),
      Bottle(
          id: 'SPRT',
          bottleName: 'Sprite',
          image: 'lib/assets/images/Sprite.jpg',
          time: 20,
          price: 274.00,
          bottletype: 'Soft Drinks'),
    ]),
    BottleCategory(categoryNo: 4, bottleType: 'Whiskey', bottleList: [
      Bottle(
          id: '',
          bottleName: 'Jameson',
          image: 'lib/assets/images/Jameson.jpg',
          time: 20,
          price: 274.00,
          bottletype: 'Whiskey'),
      Bottle(
          id: '',
          bottleName: 'Johnnie Walker Black Label',
          image: 'lib/assets/images/JohnnieWalkerBlackLabel.jpg',
          time: 20,
          price: 274.00,
          bottletype: 'Whiskey'),
    ]),
  ];

  List<Bottle> getBottleItem() {
    List<Bottle> localBottles = [];
    for (var x = 0; x < categories.length; x  ) {
      var currentElement = categories[x].bottleList;
      for (var y = 0; y < currentElement.length; y  ) {
        var bottleElement = currentElement[y];
        localBottles.add(bottleElement);
      }
    }
    print(localBottles);
    return localBottles;
  }

  late List<Bottle> allBottles = getBottleItem();
  late List<Bottle> bottles;
  String query = " ";

  void searchBottle(String query) {
    final bottles = allBottles.where((bottle) {
      final bottleNameLower = bottle.bottleName.toLowerCase();
      final searchLower = query.toLowerCase();

      return bottleNameLower.contains(searchLower);
    }).toList();

    setState(() {
      this.bottles = bottles;
      this.query = query;
    });
  }

  Widget buildSearch() {
    return SearchWidget(
      text: query,
      hintText: 'Search for beverages',
      onChanged: searchBottle,
    );
  }

  @override
  void initState() {
    super.initState();
    tabController = TabController(length: 4, vsync: this);
  }

  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var cart = Provider.of<ShoppingCartProvider>(context);
    Size _screenSize = MediaQuery.of(context).size;
    final double itemHeight = (_screenSize.height - kToolbarHeight - 24) / 2;
    final double itemWidth = _screenSize.width / 2;
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            buildSearch(),
            const Padding(
              padding: EdgeInsets.all(8.0),
              child: Text(
                'Categories',
                style: TextStyle(
                    fontSize: 20.0,
                    fontFamily: 'Montserrat-ExtraBold',
                    fontWeight: FontWeight.bold),
              ),
            ),
            Container(
              child: Align(
                alignment: Alignment.centerLeft,
                child: TabBar(
                  controller: tabController,
                  indicator:
                      CircleTabIndicator(color: Colors.redAccent, radius: 4.0),
                  isScrollable: true,
                  labelColor: Colors.redAccent,
                  labelStyle: const TextStyle(
                      fontWeight: FontWeight.bold, fontSize: 20.0),
                  unselectedLabelColor: Colors.black,
                  unselectedLabelStyle: const TextStyle(
                      fontWeight: FontWeight.bold, fontSize: 20.0),
                  tabs: const [
                    Tab(text: 'Brandy'),
                    Tab(text: 'Gin'),
                    Tab(text: 'Soft drinks'),
                    Tab(text: 'Whiskey')
                  ],
                ),
              ),
            ),
            Container(
              height: 400,
              width: double.maxFinite,
              child: TabBarView(
                  controller: tabController,
                  children: categories.map((bottleCategory) {
                    return GridView.count(
                        crossAxisCount: 2,
                        childAspectRatio: itemWidth / itemHeight,
                        children: bottleCategory.bottleList.map((bottle) {
                          return ProductItem(
                            id: bottle.id,
                            bottleName: bottle.bottleName,
                            imgUrl: bottle.image,
                            price: bottle.price,
                          );
                        }).toList());
                  }).toList()),
            ),
          ],
        ),
      ),
    );
  }
}

class SearchWidget extends StatefulWidget {
  final String text;
  final ValueChanged<String> onChanged;
  final String hintText;
  const SearchWidget(
      {Key? key,
      required this.text,
      required this.onChanged,
      required this.hintText})
      : super(key: key);

  @override
  State<SearchWidget> createState() => _SearchWidgetState();
}

    class _SearchWidgetState extends State<SearchWidget> {
      final controller = TextEditingController();
      @override
      Widget build(BuildContext context) {
        return Container(
          child: TextField(
            controller: controller,
            decoration: InputDecoration(
                icon: const Icon(
                  Icons.search,
                  color: Colors.grey,
                ),
                suffixIcon: widget.text.isNotEmpty
                    ? GestureDetector(
                        child: const Icon(Icons.close),
                        onTap: () {
                          controller.clear();
                          widget.onChanged('');
                          FocusScope.of(context).requestFocus(FocusNode());
                        },
                      )
                    : null,
                hintText: widget.hintText,
                border: InputBorder.none),
            onChanged: widget.onChanged,
          ),
        );
      }
    }

CodePudding user response:

your setState() call is changing the bottles variable but your list is displayed by the categories variable.

CodePudding user response:

First, instead of creating a new List<Bottle> allBottles, you can expand your categories to get the list of all Bottles like this :

final bottles = categories.expand((element) => element.bottleList).toList();

Then, in your GridView, you can directly use the expanded list and apply your query string :

GridView.count(
  crossAxisCount: 2,
  childAspectRatio: itemWidth / itemHeight,
  children: categories.expand((element) => element.bottleList).toList().where((element) { //Get the flatten list of bottles
    return element.bottleName.toLowerCase().contains(query.toLowerCase()); // Apply the query string
  }).map((bottle) {
    return ProductItem(
      id: bottle.id,
      bottleName: bottle.bottleName,
      imgUrl: bottle.image,
      price: bottle.price,
    );
  }).toList(),
);

Hope this helps

  • Related