1

I am building a flutter ecommerce app and I'm having an issue where if I click the add to favorites button it selects all items and not the specific item selected. My items are in a gridview using tabbarview. Please can anyone assist.

Here are the items before selection enter image description here

Then after selection (As you can see all items are selected) enter image description here

Here's my code Models:

//Bottle and Bottle Category Model classes
class Bottle with ChangeNotifier {
  String id;
  String bottleName;
  String image;
  String bottletype;
  int time;
  double price;
  bool isFavourite;
  Bottle(
      {required this.id,
      required this.bottleName,
      required this.image,
      required this.bottletype,
      required this.time,
      required this.price,
      this.isFavourite = false});

  void isFavoritePressed() {
    isFavourite = !isFavourite;
    notifyListeners();
  }
}

class BottleCategory {
  int categoryNo;
  String bottleType;
  List<Bottle> bottleList;
  BottleCategory(
      {required this.categoryNo,
      required this.bottleType,
      required this.bottleList});
}
//Cart Model Class
class Cart with ChangeNotifier {
  String id;
  String bottleName;
  String image;
  double price;
  int quantity;
  Cart({
    required this.id,
    required this.bottleName,
    required this.image,
    required this.price,
    required this.quantity,
  });
}


class ProductItem extends StatelessWidget {
  HomePage homeInstance = const HomePage();
  String id;
  String bottleName;
  String imgUrl;
  double price;
  bool isFavourite;
  ProductItem(
      {Key? key,
      required this.id,
      required this.bottleName,
      required this.imgUrl,
      required this.price,
      this.isFavourite = false})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    var cart = Provider.of<ShoppingCartProvider>(context);
    var product = Provider.of<Bottle>(context);
    return Card(
      shadowColor: Colors.grey,
      surfaceTintColor: Colors.amber,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      child: Stack(
        children: [
          Positioned(
            right: 0,
            child: InkWell(
              onTap: () {
                product.isFavoritePressed();
                if (product.isFavourite) {
                  ScaffoldMessenger.of(context).hideCurrentSnackBar();
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text(
                        "Product Added to Favorite!",
                        style: TextStyle(fontSize: 16),
                      ),
                      backgroundColor: Colors.green,
                      duration: Duration(seconds: 1),
                    ),
                  );
                } else {
                  ScaffoldMessenger.of(context).hideCurrentSnackBar();
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text(
                        "Product Removed from Favorite!",
                        style: TextStyle(fontSize: 16),
                      ),
                      backgroundColor: Colors.red,
                      duration: Duration(seconds: 1),
                    ),
                  );
                }
              },
              child: Icon(
                product.isFavourite ? Icons.favorite : Icons.favorite_border,
                size: 25,
                color: Colors.redAccent,
              ),
            ),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Center(
                child: Image.asset(
                  imgUrl,
                  height: 200.0,
                ),
              ),
              Center(
                  child: Text(bottleName,
                      style: const TextStyle(
                          fontSize: 20.0, fontWeight: FontWeight.bold))),
              Center(
                child: Text('R$price'),
              )
            ],
          ),
          Positioned(
              bottom: 0,
              right: 10,
              child: IconButton(
                icon: const Icon(Icons.add_circle),
                iconSize: 40.0,
                onPressed: () {
                  cart.addToCart(id, bottleName, price, imgUrl);
                },
              ))
        ],
      ),
    );
  }
}

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;

  String query = " ";

  Widget buildSearch() {
    return SearchWidget(
      text: query,
      hintText: 'Search for beverages',
      onChanged: (value) {
        setState(() {
          this.query = value;
        });
      },
    );
  }

  @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: productProvider.categories.map((bottleCategory) {
                    return GridView.count(
                      crossAxisCount: 2,
                      childAspectRatio: itemWidth / itemHeight,
                      children: bottleCategory.bottleList.where((element) {
                        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(),
                    );
                  }).toList()),
            ),
          ],
        ),
      ),
    );
  }
}
//ProductProvider
class ProductProvider with ChangeNotifier {
  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> get bottleItems {
    return [...bottleItems];
  }

  List<Bottle> get bottleObjects {
    return [...bottleObjects];
  }

  get bottle => null;

  List<Bottle> get selectedFavorite {
    return categories
        .expand((bottleCategory) {
          return bottleCategory.bottleList;
        })
        .toList()
        .where((bottle) {
          return bottle.isFavourite;
        })
        .toList();
  }
}

//Cart Provider
import 'package:flutter/cupertino.dart';
import 'package:test_code/cart.dart';

class ShoppingCartProvider with ChangeNotifier {
  final Map<String, Cart> _cartItems = {};

  Map<String, Cart> get cartItem {
    return {..._cartItems};
  }

  void removeBottle(String id) {
    _cartItems.remove(id);
    notifyListeners();
  }

  void addToCart(
      String id, String bottleName, double bottlePrice, String image) {
    if (_cartItems.containsKey(id)) {
      _cartItems.update(id, (existingItem) {
        return Cart(
            id: existingItem.id,
            bottleName: existingItem.bottleName,
            image: existingItem.image,
            price: existingItem.price,
            quantity: existingItem.quantity + 1);
      });
    } else {
      _cartItems.putIfAbsent(id, () {
        return Cart(
            id: id,
            bottleName: bottleName,
            image: image,
            price: bottlePrice,
            quantity: 1);
      });
    }
    notifyListeners();
  }

  void addQuantity(String id) {
    _cartItems.update(id, (existingItem) {
      return Cart(
          id: existingItem.id,
          bottleName: existingItem.bottleName,
          image: existingItem.image,
          price: existingItem.price,
          quantity: existingItem.quantity + 1);
    });
    notifyListeners();
  }

  void reduceQuantity(String id) {
    _cartItems.update(id, (existingItem) {
      if (existingItem.quantity == 1) {
        return Cart(
            id: existingItem.id,
            bottleName: existingItem.bottleName,
            image: existingItem.image,
            price: existingItem.price,
            quantity: existingItem.quantity);
      } else {
        return Cart(
            id: existingItem.id,
            bottleName: existingItem.bottleName,
            image: existingItem.image,
            price: existingItem.price,
            quantity: existingItem.quantity - 1);
      }
    });
    notifyListeners();
  }

  void clearCart() {
    _cartItems.clear();
    notifyListeners();
  }

  double get totalAmount {
    double total = 0.0;
    _cartItems.forEach((key, eachItem) {
      total = total + eachItem.price * eachItem.quantity;
    });
    return total;
  }

  int get cartCount {
    return _cartItems.length;
  }
}
Anesu Mazvimavi
  • 131
  • 1
  • 9
  • Can you simplify the widget with necessary data that will reproduce the same error. Also I cant find you've provided `isFavourite` – Md. Yeasin Sheikh Sep 01 '22 at 18:07
  • @YeasinSheikh I see what you mean there. Yeah there is a quite a bit of code I have to add on. Hope that's okay with you if you want to reproduce the same error – Anesu Mazvimavi Sep 01 '22 at 19:16

2 Answers2

1

You can play with widget to understand the item selecting mode.

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

  @override
  State<HomePageX> createState() => _HomePageXState();
}

class _HomePageXState extends State<HomePageX> {
  List<int> selectedIndex = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GridView.builder(
        gridDelegate:
            SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
        itemBuilder: (context, index) {
          return ProductItem(
            isFavorite: selectedIndex.contains(index),
            onTap: () {
              if (selectedIndex.contains(index)) {
                selectedIndex.remove(index);
              } else {
                selectedIndex.add(index);
              }

              setState(() {});
            },
          );
        },
      ),
    );
  }
}

class ProductItem extends StatelessWidget {
  final bool isFavorite;
  final VoidCallback onTap;
  const ProductItem({
    Key? key,
    required this.isFavorite,
    required this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      child: Card(
        child: Container(
          color: isFavorite ? Colors.blue : Colors.grey,
        ),
      ),
    );
  }
}
Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • Thank you let me try that. But I have now edited the code if you want to have a look at the full thing. It's got all the model classes – Anesu Mazvimavi Sep 01 '22 at 19:30
0

I solved this problem for myself. I just moved the button to a separate widget. I don't know how it works, but it helped me.

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

  @override
  State<FavoriteButtonWidget> createState() => _FavoriteButtonWidgetState();
}

class _FavoriteButtonWidgetState extends State<FavoriteButtonWidget> {
  late Box box;
  late bool _isFavorite;

  @override

  void initState() {
    super.initState();
    box = Hive.box('favorites');
    final data = box.get("favorites");
    _isFavorite = data ?? false;
  }

  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: () {
        setState(() {
          _isFavorite = !_isFavorite;
        });
        box.put("favorites", _isFavorite);
      },
      icon: _isFavorite
          ? const Icon(
              Icons.favorite_border,
              color: UiKitColors.red,
            )
          : const Icon(
              Icons.favorite,
              color: UiKitColors.red,
            ),
    );
  }
}