Home > Mobile >  Flutter firebase Getx shopping cart: How do I increase the quantity of items without getting duplica
Flutter firebase Getx shopping cart: How do I increase the quantity of items without getting duplica

Time:09-21

So right now I'm building a shopping cart using GetX and firebase. I'm trying to write the data to firebase if the user taps add to cart but not allow duplicate entries just increase the quantity #. Right now I'm getting duplicate entries.

class CartController extends GetxController {
  static CartController instance = Get.find();
  //cart controller for the shopping cart
  Rx<double> totalCartPrice = 0.0.obs;
  RxList<CartItemModel> products = RxList<CartItemModel>([]);
  @override
  void onReady() {
    super.onReady();
    ever(logController.userModel, editTotalPrice);
  }

  void addToCart(ProductModel product) {
    try {
      if (_isItemAlreadyAdded(product)) {
        Get.snackbar("Check your cart", "${product.name} is already added");
      } else {
        String itemID = Uuid().v1();
        logController.updateUserData({
          "cart": FieldValue.arrayUnion([
            {
              "id": itemID,
              "productId": product.id,
              // "name": product.name,
              "quantity": 1,
              "price": product.price,
              "image": product.image,
              "cost": product.price,
            }
          ])
        });
        Get.snackbar("Item added", "${product.name} was added to your cart");
      }
    } catch (e) {
      Get.snackbar("Whoops...Cannot add this right now!",
          "There was an error adding to cart",
          duration: Duration(seconds: 3), backgroundColor: Colors.pinkAccent);
      debugPrint(e.toString());
    }
  }

  void deductCartItem(CartItemModel cartItem) {
    try {
      logController.updateUserData({
        "cart": FieldValue.arrayRemove([cartItem.toJson()])
      });
    } catch (e) {
      Get.snackbar("Error", "Cannot remove this item");
      debugPrint(e.toString());
    }
  }

  editTotalPrice(LoginUserModel usrModel) {
    if (usrModel.cart!.isEmpty) {
      print("Cart empty!");
    } else if (usrModel.cart!.isNotEmpty) {
      totalCartPrice.value = 50;
      print("hi");
      usrModel.cart!.forEach((cartItem) {
        totalCartPrice.value  = cartItem.cost!;
      });
    }
  }

  bool _isItemAlreadyAdded(ProductModel product) =>
      logController.userModel.value.cart!
          .where((item) => item.name == product.name)
          .isNotEmpty;

  void decreaseQuantity(CartItemModel item) {
    if (item.quantity == 1) {
      deductCartItem(item);
    } else {
      deductCartItem(item);
      item.quantity! - 1;
      logController.updateUserData({
        "cart": FieldValue.arrayUnion([item.toJson()])
      });
    }
  }

  void increaseQuantity(CartItemModel item) {
    deductCartItem(item);
    item.quantity!   1;
    logger.i({"quantity": item.quantity});
    logController.updateUserData({
      "cart": FieldValue.arrayUnion([item.toJson()])
    });
  }
}

and here is my class:

class CartItemModel {
  CartItemModel(
      {this.productId,
      this.id,
      this.image,
      this.name,
      this.quantity,
      required this.price,
      this.cost});

  CartItemModel.fromMap(Map<String, dynamic> data) {
    id = data[ID];
    image = data[IMAGE];
    name = data[NAME];
    quantity = data[QUANTITY];
    cost = data[COST].toDouble();
    productId = data[PRODUCT_ID];
    price = data[PRICE];
  }

  static const COST = "cost";
  static const ID = "id";
  static const IMAGE = "image";
  static const NAME = "name";
  static const PRICE = "price";
  static const PRODUCT_ID = "productId";
  static const QUANTITY = "quantity";

  double? cost;
  String? id;
  String? image;
  String? name;
  double? price;
  String? productId;
  int? quantity;

  Map toJson() => {
        ID: id,
        PRODUCT_ID: productId,
        IMAGE: image,
        NAME: name,
        QUANTITY: quantity,
        COST: price! * quantity!,
        PRICE: price
      };
}

and because this is tied into the login session here is my login class:

class LoginUserModel {
  String? displayName;
  String? email;
  String? photoUrl;
  String? uid;
  List<CartItemModel>? cart;

  LoginUserModel(
      {this.displayName, this.email, this.photoUrl, this.uid, this.cart});

  LoginUserModel.fromSnapshot(DocumentSnapshot<Map<String, dynamic>> snapshot) {
    displayName = snapshot.data()!["DISPLAYNAME"];
    photoUrl = snapshot.data()!["PHOTOURL"];
    email = snapshot.data()!["EMAIL"];
    uid = snapshot.data()!["UID"];
    cart = _convertCartItems(snapshot.data()!["CART"] ?? []);
  }

  List<CartItemModel> _convertCartItems(List cartFomDb) {
    List<CartItemModel> _result = [];
    if (cartFomDb.length > 0) {
      cartFomDb.forEach((element) {
        _result.add(CartItemModel.fromMap(element));
      });
    }
    return _result;
  }

  List cartItemsToJson() => cart!.map((item) => item.toJson()).toList();
}

And my login controller:

class LoginController extends GetxController {
  static LoginController instance = Get.find();

  Rxn<User> fbUser = Rxn<User>();
  final googleSignIn = GoogleSignIn();
  RxBool isLoggedIn = false.obs;
  Rx<LoginUserModel> userModel = LoginUserModel().obs;
  String usersCollection = "users";
  // Rx<UserModel> usrModel = UserModel().obs;

  GoogleSignInAccount? _googleAcc;
  LoginUserModel? _userModel;

  @override
  void onReady() {
    super.onReady();
    fbUser = Rxn<User>(auth.currentUser);
    fbUser.bindStream(auth.userChanges());
    ever(fbUser, setInitialScreen);
  }

  LoginUserModel? get loggedInUserModel => _userModel;

  setInitialScreen(User? user) {
    if (user == null) {
      print("going to login page...");
      Get.offAll(() => LoginPage());
    } else {
      print("The user is ${user.displayName}");
      userModel.bindStream(listenToUser());
      Get.offAll(() => AppSetup());
    }
  }

  void googleLogin() async {
    final googleUser = await googleSignIn.signIn();
    if (googleUser == null) return;

    _googleAcc = googleUser;
    final googleAuth = await googleUser.authentication;

    final cred = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    try {
      await auth.signInWithCredential(cred).then((res) async {
        print('Signed in successfully as '   res.user!.displayName.toString());
        print('email: '   res.user!.email.toString());
        LoginUserModel _newUser = LoginUserModel(
          uid: res.user!.uid,
          email: res.user!.email!,
          displayName: res.user!.displayName,
          photoUrl: res.user!.photoURL,
          cart: [],
        );
        _addUserToFB(_newUser, res.user!);
      });
    } catch (e) {
      debugPrint(e.toString());
      Get.snackbar("Sign In Failed", "Try again");
    }
  }

  // void signUp() async {
  //   try {
  //     await auth
  //         .createUserWithEmailAndPassword(
  //             email: email.text.trim(), password: password.text.trim())
  //         .then((result) {
  //       String _userId = result.user.uid;
  //       _addUserToFirestore(_userId);
  //       _clearControllers();
  //     });
  //   } catch (e) {
  //     debugPrint(e.toString());
  //     Get.snackbar("Sign In Failed", "Try again");
  //   }
  // }

  void signOut() async {
    googleSignIn.signOut();
    auth.signOut();
  }

  //maybe add clear controllers method?

  updateUserData(Map<String, dynamic> data) {
    logger.i("UPDATED");
    firebaseFirestore
        .collection(usersCollection)
        .doc(fbUser.value?.uid)
        .update(data);
  }

  Stream<LoginUserModel> listenToUser() => firebaseFirestore
      .collection(usersCollection)
      .doc(fbUser.value!.uid)
      .snapshots()
      .map((snapshot) => LoginUserModel.fromSnapshot(snapshot));

  _addUserToFB(LoginUserModel usr, User firebaseUser) {
    firebaseFirestore.collection(usersCollection).doc(usr.uid).set({
      "displayName": usr.displayName,
      "id": usr.uid,
      "photoURL": usr.photoUrl,
      "email": usr.email,
      "cart": usr.cart
    });
  }
}

CodePudding user response:

you can use collection.addWhere(CartItemModel.id,notEqual("id"));

CodePudding user response:

It looks like the issue is in your arrayUnion call because you are adding a new entry to the shopping cart array. There is perhaps an expectation that the method will find and update the provided object in the array.

What you would need to do is to find and replace/update the cart item in your local user object array and make a document update back to Firestore.

But instead of doing that, I'd like to suggest something you could consider: you might want to consider making the shopping cart a subcollection instead.

For two reasons

  1. You have an unbound growing data-set (shopping cart items). That is making a good candidate for being a collection of its own.
  2. You want to be able to update field values in individual documents (shopping cart items) without causing too much other "noise" in your app. When you store the shopping cart in your user object and update items in the array, you are also causing any listener that is subscribing to your user object to trigger a new read from the database.

Implementing your shopping cart as a sub collection allows for you to do this instead

Future<void> increaseQuantity(CartItemModel item) {
    return FirebaseFirestore.instance
        .collection("users")
        .doc(userId)
        .collection("cart")
        .doc(item.id)
        .update({"quantity": FieldValue.increment(1)});
  }

You would do the same for decreaseQuantity and update quantity with FieldValue.increment(-1) instead.

  • Related