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