Provider with GridView Builder

824 Views Asked by At

I am making an online store where there is a discount section and product categories on the mvvm architecture, the goal is to change the quantity of the added product to the cart, everything works except that the quantity changes for all products, but everything is displayed correctly in the database, I'm sure somewhere I missed something important, I will be grateful for the answer, I will attach screenshots below

RootPage

return MultiProvider(
    providers: [
      ChangeNotifierProvider(
        create: (context) => MainPageListViewModel(),
      ),
      ChangeNotifierProvider(
          create: (context) => CartViewModel(),
          child: CartPage()
      ),
      ChangeNotifierProvider(
        create: (context) => AllGoodsViewModel(),
      ),
    ],
  child: MaterialApp(
      initialRoute: '/',
      routes: {
        '/ProfilePage': (context) => ProfilePage(),
        '/MainPage': (context) => MainPage(),
        '/CartPage': (context) =>   CartPage(),
      },
      builder: (context, widget) {
        return ScreenUtilInitService(
            builder: (context) => widget!
        );
      },
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: present != null ? present : MainPage()
  ),
);

CategoryPage

class AllCategoryGoodsPage extends StatefulWidget {

const AllCategoryGoodsPage({key, required this.category}) : super(key: key);

final CategoryData category;

@override
State<AllCategoryGoodsPage> createState() => _AllCategoryGoodsPageState();

}

class _AllCategoryGoodsPageState extends State<AllCategoryGoodsPage> {

@override
void initState() {
 super.initState();
 Provider.of<AllGoodsViewModel>(context, listen: false).fetchAllItems(id: widget.category.id);
}

@override
Widget build(BuildContext context) {

 final model = Provider.of<AllGoodsViewModel>(context);

 return MaterialApp(
   home: Scaffold(
     appBar: AppBar(),
     body: Container(
         width: MediaQuery.of(context).size.width,
         height: MediaQuery.of(context).size.height,
         color: Colors.white,
         child: GridView.builder(
             shrinkWrap: true,
             physics: BouncingScrollPhysics(),
             gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                 crossAxisCount: 2,
                 childAspectRatio: 0.76),
             itemCount: model.goods.length,
             itemBuilder: (context, index) {
               return ViewGoods(model: model.goods[index], index: index);
             }),
     ),
   ),
 );
}

}

ViewGoods

class _ViewGoodsState extends State<ViewGoods> {

  GlobalKey _key = GlobalKey();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_){
      Provider.of<AllGoodsViewModel>(context, listen: false).setting(widget.model);
    });
  }

  @override
  Widget build(BuildContext context) {

    final model = Provider.of<AllGoodsViewModel>(context);
    final size = MediaQuery.of(context).size;

    Widget inCart(){
      return Container(
        key: _key,
        height: 31,
        child: GestureDetector(
          onPanDown: (details) {
            Goods? item = widget.model;
            RenderBox _cardBox = _key.currentContext!.findRenderObject() as RenderBox;
            final localPosition = details.localPosition;
            final localDx = localPosition.dx;
            if (localDx <= _cardBox.size.width/2) {
              Goods value = cart.firstWhere((element) => element.id == item.id);
              if (item.optState == 0 ? value.orderCount <= 1 : value.orderCount <= value.opt!.count) {
                setState(() {
                  context.read<AllGoodsViewModel>().setCountInCart(0);
                  final ind = cart.indexWhere((element) => element.id == item.id);
                  if (ind != -1) {
                    cart[ind].orderCount = 0;
                    SQFliteService.cart.delete(cart[ind].id);
                    cart.removeAt(ind);
                    NotificationCenter().notify("cart");
                  }
                });
              } else {
                model.haveItem(item: item, operation: item.optState == 0 ? -1 : (-1 * value.opt!.count));
              }
            } else {
              model.haveItem(item: item, operation: item.optState == 0 ? 1 : item.count);
            }

          },
          child: TextButton(
            style: ButtonStyle(
                backgroundColor: MaterialStateProperty.all(Design.appColor),
                padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 8, horizontal: 10)),
                shape: MaterialStateProperty.all(RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10.0),
                ))
            ),
            onPressed: (){},
            child: Container(
              child: RichText(
                text:  TextSpan(
                  text: "",
                  children:[
                    WidgetSpan(
                      alignment: PlaceholderAlignment.middle,
                      child: Icon(Icons.remove, size: 14, color: Colors.white),
                    ),
                    TextSpan(
                      text: "  ${widget.model.optState == 0 ? (widget.model.minPrice ?? widget.model.price) : widget.model.opt!.price} ₽  ",
                      style: TextStyle(
                          color: Colors.white,
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                          fontFamily: "Inter"
                      ),
                    ),
                    WidgetSpan(
                      alignment: PlaceholderAlignment.middle,
                      child: Icon(Icons.add, size: 14, color: Colors.white),
                    )
                  ],
                ),
              ),
            ),
          ),
        ),// Your TextButton code goes here.
      );
    }

    Widget noInCart(){
      return Container(
        key: _key,
        height: 31,
        child: TextButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(model.orderBg),
              padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 8, horizontal: 10)),
              shape: MaterialStateProperty.all(RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10.0),
              ))
          ),
          onPressed: (){
            Goods? item = widget.model;
            model.haveItem(item: item, operation: item.optState == 0 ? 1 : item.count);
          },
          child: Container(
            child: RichText(
              text:  TextSpan(
                text: "${widget.model.optState == 0 ? widget.model.minPrice == null ? widget.model.price : widget.model.minPrice : widget.model.opt!.price} ₽ ",
                style: TextStyle(
                    color: widget.model.minPrice != null ? Design.grey : Colors.black,
                    decoration: widget.model.optState == 0 && widget.model.minPrice != null ? TextDecoration.lineThrough : TextDecoration.none,
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                    fontFamily: "Inter"

                ),
                children:[
                  TextSpan(
                    text: widget.model.minPrice == null ? "" : " ${widget.model.price} ₽",
                    style: TextStyle(
                        color: Colors.black,
                        decoration: TextDecoration.none,
                        fontSize: 14,
                        fontWeight: FontWeight.w500,
                        fontFamily: "Inter"
                    ),
                  ),

                  WidgetSpan(
                    alignment: PlaceholderAlignment.middle,
                    child: Icon(Icons.add, size: 14, color: Colors.black),
                    style: TextStyle(
                      color: Colors.black,
                      decoration: TextDecoration.none,
                    ),
                  )
                ],
              ),
            ),
          ),
        ),
      );
    }


    Widget card({required Size size}) {
      return Container(
        color: Colors.white,
        width: (size.width/2.4) - 11,
        margin: EdgeInsets.only(right: (widget.index.isOdd ? 16 : 5) , left: (widget.index.isOdd ? 5 : 16)),
        child: Column(
          children: [
            Stack(
                children: [
                  Container(
                    height: (size.width/2 - 16),
                    width: (size.width/2),
                    padding:  EdgeInsets.all(1),
                    decoration: BoxDecoration(
                      image: DecorationImage(
                          fit: BoxFit.cover,
                          image: NetworkImage(widget.model.images.first)
                      ),
                      borderRadius: BorderRadius.all(Radius.circular(10.0)),
                      border: Border.all(
                          width: 1,
                          color: Design.lightGrey
                      ),
                    ),
                  ),
                  Visibility(
                    visible: model.countInCart == 0 ? false : true,
                    child: Container(
                      height: (size.width/2 - 16),
                      width: (size.width/2),
                      decoration: BoxDecoration(
                        color: Colors.black.withOpacity(0.5),
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                      ),
                      child: Visibility(
                        visible: true,
                        child: Center(
                          child: model.orderCountText,
                        ),
                      ),
                    ),
                  )
                ]
            ),
            SizedBox(height: 5),
            Align(
              alignment: Alignment.centerLeft,
              child: Container(
                height: 34.h,
                child: Text(widget.model.name,
                  maxLines: 2,
                  style: TextStyle(
                    fontSize: 14.sp,
                    fontWeight: FontWeight.w500,
                    fontFamily: "Inter",
                  ),
                ),
              ),
            ),
            SizedBox(height: 5),
            Align(
                alignment: Alignment.centerLeft,
                child: (context.read<AllGoodsViewModel>().countInCart == 0) ? noInCart() : inCart()
            )
          ],
        ),
      );
    }

    return card(size: size);
  }

}

and ViewModel

class AllGoodsViewModel extends ChangeNotifier {

  List<Goods> goods = List.empty(growable: true);
  late DocumentSnapshot documentSnapshot;

  Future<void> fetchAllItems({required String id}) async {
    final data = await FirestoreService.shared.fetchItems(id);
    this.goods = [];
    sortGoods(data);
  }

  Future<void> paginateAllGoods({required String id}) async {
    var data = await FirestoreService.shared.fetchRequestItems(documentSnapshot: documentSnapshot, id: id);
    sortGoods(data);
  }

  Future<void> sortGoods(Tuple? data) async {
    if (data != null) {
      if (data.data.length == 0) {
        return;
      }
      this.documentSnapshot = data.id;
      final items = data.data as List<QueryDocumentSnapshot<Map<String, dynamic>>>;
      this.goods.addAll(items.map((e) => Goods.fromFirestoreTo(e)).toList());
      notifyListeners();
    }
  }


  var _countInCart = 0;
  int get countInCart => _countInCart;

  var _orderBg = Design.lightGrey;
  Color get orderBg => _orderBg;

  var _orderColor = Design.paleWhite;
  Color get orderColor => _orderColor;

  var _orderStyle;
  TextStyle get orderStyle => _orderStyle;

  var _orderCountText = Text("0");
  Text get orderCountText => _orderCountText;

  void setting(Goods item) {

    final ind = cart.indexWhere((element) => element.id == item.id);
    if (ind != -1) {
      item.optState = cart[ind].isOpt ? 1 : 0;
      // chooseView.configure(item: item, tag: order.tag)
      setReadyData(index: ind);
    } else {
      //chooseView.configure(item: item, tag: 0)
      setCountInCart(0);
    }
  }

  // add and remove product
  void haveItem({required Goods item, required int operation}) async {
    final ind = cart.indexWhere((element) => element.id == item.id);
    if (ind == -1) {
      final minCount = item.optState == 0 ? 1 : item.opt!.count;
      if (item.count < minCount) {
        //order.shake();
      } else {
        cart.add(item);
        final ind = cart.length - 1;
        cart.last.isOpt = item.optState == 0 ? false : true;
        cart.last.orderCount = minCount;
        await SQFliteService.cart.addToCart(cart.last);
        // animate();
        NotificationCenter().notify("cart");
        changeCountInCart(operation);
        countText(index: ind, item: cart.last);
        orderText();
      }
    } else {
      final count = cart[ind].orderCount;
      if (count <= item.count) {} else { return; } //order.shake()
      if (operation < 0 || count + operation <= item.count) {} else { return; } //order.shake()
      cart[ind].orderCount += operation;
      SQFliteService.cart.updateItem(cart[ind].id, {"orderCount":cart[ind].orderCount});
      NotificationCenter().notify("cart");
      changeCountInCart(operation);
      countText(index: ind, item: cart[ind]);
    }
  }


  // when re-displaying a product
  void setReadyData({required int index}) {
    final empty = cart[index].orderCount == 0;
    empty ? defaultOrderText() : orderText();
    if (empty) { return; }
    setCountInCart(cart[index].orderCount);
    countText(item: cart[index]);
  }

  //quantity of goods in the basket
  void countText({int? index, required Goods item}) {
    var ind = index;
    if (index == null) {
      ind = cart.indexWhere((element) => element.id == item.id);
    }
    if (ind == -1) { return; }
    if (cart[ind!].orderCount >= item.count){
      final text = Text.rich(
        TextSpan(
          style: TextStyle(
            fontSize: 22,
            fontFamily: "Inter",
            fontWeight: FontWeight.w400,
            color: Colors.white,
          ),
          text: "$_countInCart",
          children: <TextSpan>[
            TextSpan(text: "\nНет в наличии", style: TextStyle(
                fontSize: 16,
                fontFamily: "Inter",
                fontWeight: FontWeight.bold,
                color: Colors.white
            )),
          ],
        ),
        textAlign: TextAlign.center,
      );
      changeOrderCountText(text);
    } else {
      final text = Text("$_countInCart",
        style: TextStyle(
            fontSize: 22,
            fontFamily: "Inter",
            fontWeight: FontWeight.w400,
            color: Colors.white
        ),
      );
      changeOrderCountText(text);
    }

  }

  void changeOrderCountText(Text widget){
    _orderCountText = widget;
  }

  void defaultOrderColor(){
      _orderColor = Design.paleWhite;
      _orderStyle = TextStyle(color: Design.dark);
  }

  // add button text
  void orderText() {
    _orderColor = Design.paleWhite;
    _orderStyle = TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 14);
    if (_orderBg == Design.paleWhite) {
      _orderBg = Design.appColor;
    }
  }

  void defaultOrderText() {
    _orderBg = Design.paleWhite;
    _orderColor = Design.dark;
    _orderStyle = TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 14);
  }

  void setCountInCart(int value){
    _countInCart = value;
    notifyListeners();
  }

  void changeCountInCart(int value){
    _countInCart += value;
    notifyListeners();
  }

}

the whole problem is that when you change by clicking on the button, the quantity changes for all products

enter image description here enter image description here enter image description here

1

There are 1 best solutions below

4
MerdanDev On

I think problem is in you ChangeNotifierProvider of AllGoodsViewModel():

ChangeNotifierProvider(
    create: (context) => AllGoodsViewModel(),
),

Because you use it glabally, and everywhere you call it with context it will return same model, that is why every product show same count.

I recommend you use ChangeNotifierProvider.value() by wrapping each widget in list of products:

ChangeNotifierProvider.value(
  value: MyModel(),
  child: ...
)

And you don't need to provide your model globally.