Provider with GridView Builder

138

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.

Share:
138
Vasya2014
Author by

Vasya2014

Updated on December 01, 2022

Comments

  • Vasya2014
    Vasya2014 11 months

    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

  • Vasya2014
    Vasya2014 over 1 year
    I did it globally to get data from other widgets, I have a lot of nav stack
  • MerdanDev
    MerdanDev over 1 year
    I mean that you use one object for all products in list, and when you add one product into cart it increments on all products that is why your all products show same count, I have mentioned that you need to provide one object of your model for each product in your list. That why I recomended to use ChangeNotifierProvider.value() for your model. You can check more about it in pub.dev/packages/provider ChangeNotifierProvider.value
  • Vasya2014
    Vasya2014 over 1 year
    I did this, now I can't change the quantity in the discount section through the category if the product is present in both categories
  • MerdanDev
    MerdanDev over 1 year
    If you storing products in category model this can be problem. I recommend to you use cart model and give access product's count from cart model to get count for your product model. Yes I not good at mvvm design pattern, because I use ui - application - data for small projects or DDD for big projects. Actually mvvm is interesting and c# apps are build with it.