Flutter: How to Query List Items using GetX

Issue

I am trying to filter according to the category but it’s displaying all products on each category page, but I want to filter according to the category page, please check my code and let me know how I can do it.

class CategoryNavigationController extends GetxController {

  final List<CategoryModel> _categories = allCategories;

  List<CategoryModel> get allCategory => _categories;

  int currentCategoryIndex;
  String currentCategoryTitle;
  List<Widget> totalCategoryTabs = [];
  List<ProductModel> _filtertedProducts;
  Widget currentTab;

  @override
  void onInit() {
    super.onInit();
    totalCategoryTabs = List.generate(allCategory.length, (index) {
      return ProductCardList(
        totalProducts: 1,
        productName: "WHy",
        productImage: "assets/images/chicken.png",
        price: "150",
        scrollDirection: Axis.vertical,
      );
    });
  }

  void selectCategory(int index) {
    currentCategoryIndex = index;
    currentCategoryTitle = allCategory[index].categoryName;
    _filterProductData(currentCategoryTitle);
    currentTab = totalCategoryTabs[index];
    Get.to(() => CategoryScreen());
    update();
  }

  void changeTab(int index) {
    currentCategoryIndex = index;
    currentCategoryTitle = allCategory[currentCategoryIndex].categoryName;
    _filterProductData(currentCategoryTitle);
    currentTab = totalCategoryTabs[currentCategoryIndex];
    update();
  }

  void _filterProductData(query) {
    print(query);
  }
}

Here is my data model. I want to rebuild my current TabView every time when user drag tab view or tap on the tab button

class CategoryModel {
  String categoryName;
  String categoryImage;
  String categoryIcon;

  CategoryModel({
    this.categoryName,
    this.categoryImage,
    this.categoryIcon,
  });
}

List<CategoryModel> allCategories = <CategoryModel>[
  CategoryModel(
    categoryName: "Chicken",
    categoryImage: "assets/images/chicken.png",
    categoryIcon: "assets/svg/chicken.svg",
  ),
  CategoryModel(
    categoryName: "Fish",
    categoryImage: "assets/images/sea_fish.png",
    categoryIcon: "assets/svg/fish.svg",
  ),
  CategoryModel(
    categoryName: "Mutton",
    categoryImage: "assets/images/mutton.png",
    categoryIcon: "assets/svg/mutton.svg",
  ),
  CategoryModel(
    categoryName: "Marinade",
    categoryImage: "assets/images/marinade.png",
    categoryIcon: "assets/svg/premium_meat.svg",
  ),
  CategoryModel(
    categoryName: "Cold Cut",
    categoryImage: "assets/images/sea_fish.png",
    categoryIcon: "assets/svg/cold_cut.svg",
  ),
  CategoryModel(
    categoryName: "Prone",
    categoryImage: "assets/images/sea_fish.png",
    categoryIcon: "assets/svg/fish.svg",
  ),
];

class ProductModel {
  String productName;
  String productImage;
  CategoryModel category;
  String price;
  String stock;

  ProductModel({
    this.productName,
    this.category,
    this.price,
    this.stock,
    this.productImage,
  });
}

List<ProductModel> allProducts = <ProductModel>[
  ProductModel(
    category: allCategories[0],
    productName: "chicken1",
    productImage: "assets/images/chicken.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[0],
    productName: "chicken2",
    productImage: "assets/images/chicken.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[1],
    productName: "fish1",
    productImage: "assets/images/fish.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[1],
    productName: "fish2",
    productImage: "assets/images/fish.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[2],
    productName: "mutton1",
    productImage: "assets/images/mutton.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[2],
    productName: "mutton2",
    productImage: "assets/images/mutton.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[3],
    productName: "marinade1",
    productImage: "assets/images/marinade.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[3],
    productName: "marinade2",
    productImage: "assets/images/marinade.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[4],
    productName: "ColdCut1",
    productImage: "assets/images/sea_fish.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[4],
    productName: "ColdCut2",
    productImage: "assets/images/sea_fish.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[5],
    productName: "prone1",
    productImage: "assets/images/sea_fish.png",
    price: "150",
  ),
  ProductModel(
    category: allCategories[5],
    productName: "prone2",
    productImage: "assets/images/sea_fish.png",
    price: "150",
  ),
];

This is my Category screen, Where I use TabBar & TabView.

GetBuilder<CategoryNavigationController>(
      builder: (controller) => Scaffold(
        appBar: AppBar(
          toolbarHeight: AppConfig.screenHeight(context) * 0.06,
          centerTitle: true,
          elevation: 0,
          backgroundColor: Colors.white,
          title: Text(
            "MENU",
            style: TextStyle(
              color: Color(0xff1F1F1F),
              fontWeight: FontWeight.w600,
              fontStyle: FontStyle.normal,
            ),
          ),
        ),
        body: DefaultTabController(
          initialIndex: controller.currentCategoryIndex,
          length: controller.allCategory.length,
          child: Container(
            width: double.infinity,
            height: double.infinity,
            child: Column(
              children: [
                Container(
                  height: AppConfig.screenHeight(context) * 0.1,
                  decoration: BoxDecoration(
                    border: Border(
                      bottom: BorderSide(
                        color: AppConfig.disableColor,
                        width: 1,
                      ),
                    ),
                    boxShadow: [
                      BoxShadow(
                        color: AppConfig.disableColor.withOpacity(0.5),
                        offset: Offset(0, 1),
                        spreadRadius: 1,
                        blurRadius: 1,
                      ),
                    ],
                    color: Colors.white,
                  ),
                  child: TabBar(
                    controller: controller.tabController,
                    onTap: (index) {
                      controller.changeTab();
                    },
                    isScrollable: true,
                    indicator: BoxDecoration(
                      color: AppConfig.disableColor,
                      border: Border(
                        bottom: BorderSide(
                          color: AppConfig.primaryColor,
                        ),
                      ),
                    ),
                    tabs: List.generate(
                      controller.allCategory.length,
                      (index) {
                        return CategoryButtons(
                          buttonIcon: SvgPicture.asset(
                            controller.allCategory[index].categoryIcon,
                            width: AppConfig.screenWidth(context) * 0.085,
                          ),
                          buttonName:
                              controller.allCategory[index].categoryName,
                        );
                      },
                    ),
                  ),
                ),
                Expanded(
                  child: TabBarView(
                    controller: controller.tabController,
                    children:
                        controller.allCategory.map((CategoryModel category) {
                      return controller.currentTab;
                    }).toList(),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );

Solution

Ok so for starters, lets address this:

final List<CategoryModel> _categories = allCategories;

List<CategoryModel> get allCategory => _categories;

This is not doing anything of value. You’re declaring a private variable and initializing it to a top level global variable. Then using a public getter to access a private variable that again, is nothing more than the global variable you already declared.

So 2 things here.

  1. I’m contrast to other languages, in Dart, there’s nothing to be gained from using getters and setters, UNLESS, you’re adding custom logic to getting or setting a value.

  2. There’s no reason the allCategories  list needs to be global. You can just put it in the GetX class and it’s still very easily accessible from anywhere in the app.  So let’s simplify this and just put that list in the class and lose the getters. Now it’s one variable instead of 3.

Generally, unless there’s a valid reason to make something global, best not to do so.

As for the actual solution. There are different ways to achieve this but here’s how I would do it:

Instead of filtering through a list and rebuilding everything every time, I just set up a separate list for each category and the addProduct function adds it to the right list based on the category. This way, the only thing that gets rebuilt is the corresponding tab. I left a master product list in case you need it for something else, but it’s not being used for anything that I’m doing here.

  List<ProductModel> masterProductList = [];

  List<ProductModel> chickenList = [];
  List<ProductModel> fishList = [];
  List<ProductModel> muttonList = [];
  List<ProductModel> marinadeList = [];
  List<ProductModel> coldCutList = [];
  List<ProductModel> proneList = [];

In addition, the function is using the tabController (which looks like you removed, but I put it back) to jump to the corresponding tab. This may or may not be the desired behavior, but it’s just to show you a super easy way to manage tabs with Getx. The animateTo function is not specific to GetX, but easily sharing a tab controller across your whole app, is. Here’s the addProduct function that goes in your GetX class.

If you have some massive product list that I’m not aware of, you can still loop through it and pass everything through this function and it will sort it.

void addProduct(ProductModel product) {
    masterProductList.add(product);
    switch (product.category.categoryName) {
      case 'Chicken':
        chickenList.add(product);
        tabController.animateTo(0); // this jumps to whatever tab you want based on the index you pass in
        break;
      case 'Fish':
        fishList.add(product);
        tabController.animateTo(1);
        break;
      case 'Mutton':
        muttonList.add(product);
        tabController.animateTo(2);
        break;
      case 'Marinade':
        marinadeList.add(product);
        tabController.animateTo(3);
        break;
      case 'Cold Cut':
        coldCutList.add(product);
        tabController.animateTo(4);
        break;
      case 'Prone':
        proneList.add(product);
        tabController.animateTo(5);
        break;
    }
    update();
  }

Since I don’t have all your UI code, here is a much simplified version to show you how it works. You can implement this into your app however you see fit.

Here’s the TabHome. Nothing here gets rebuilt, its just a host for the tabs. The GetView<CategoryNavigationController> is just a stateless widget that saves us from having to find that controller on that page.

class TabHome extends GetView<CategoryNavigationController> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: controller.allCategories.length,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: controller.totalCategoryTabs,
            controller: controller.tabController,
          ),
          title: Text('Categories'),
        ),
        body: TabBarView(
          controller: controller.tabController,
          children: [
            ProductPage(list: controller.chickenList), // passing in corresponding list from GetX class
            ProductPage(list: controller.fishList),
            ProductPage(list: controller.muttonList),
            ProductPage(list: controller.marinadeList),
            ProductPage(list: controller.coldCutList),
            ProductPage(list: controller.proneList),
          ],
        ),
      ),
    );
  }
}

Here’s a very basic product page that gets passed in a list that gets passed into the ListView.builder within the GetBuilder widget.

class ProductPage extends StatelessWidget {
  final List list;

  const ProductPage({Key key, this.list}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          GetBuilder<CategoryNavigationController>(
            builder: (controller) => Expanded(
              child: ListView.builder(
                itemCount: list.length,
                itemBuilder: (context, index) => ProductRow(product: list[index]),
              ),
            ),
          ),
          ButtonRow(),
        ],
      ),
    );
  }
}

Very basic Row that takes a ProductModel

class ProductRow extends StatelessWidget {
  final ProductModel product;

  const ProductRow({Key key, this.product}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Row(children: [Text(product.productName)]);
  }
}

The ButtonRow that you see in the ProductPage is a widget with 6 of these each adding a product from a different category, calling the same addProduct function but adding in a different product.

 Expanded(
              child: ElevatedButton(
                onPressed: () {
                  controller.addProduct(
                    ProductModel(
                      productName: 'boneless chicken',
                      category: CategoryModel(categoryName: 'Chicken'),
                    ),
                  );
                },
                child: Text('Add chicken'),
              ),
            ),

And finally, a couple small changes to your onInit. Be sure to add with SingleGetTickerProviderMixin to your GetX class and that lets you work with animation and tab controllers in stateless widgets.

  @override
  void onInit() {
    super.onInit();
    totalCategoryTabs = List.generate(allCategories.length, (index) {
      return ProductCardList(
        totalProducts: 1,
        productName: allCategories[index].categoryName, // auto names your tabs based on allCategories list
        productImage: "assets/images/chicken.png",
        price: "150",
        scrollDirection: Axis.vertical,
      );
    });
    tabController =
        TabController(length: totalCategoryTabs.length, vsync: this); // made possible with SingleGetTickerProviderMixin 
  }

And here is a quick demo. The GUI is not sexy, but you get the idea.

enter image description here

Answered By – Loren.A

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *