skip to Main Content

When I add a product to the cart, it calculates the total price without changing the amount of the product in the cart.

This is how I add products to the cart:

@Published var cartProducts = [CartProduct]()
private let httpDownloader = HttpDownloader()
@Published var cartItemCount = 0
@Published var totalAmount: Double = 0

func getUserProducts() {
    httpDownloader.fetchUserProducts() { (result) in
        switch result {

        case .success(let cartProducts):
            if let cartProducts = cartProducts {
                DispatchQueue.main.async {
                    self.cartProducts = cartProducts
                }
            }

        case .failure(let error):
            print("Error fetching products: (error)")
        }
    }
}

func addProductToCart(id: Int, quantity: Int) {
    httpDownloader.addProductToCart(productId: id, quantity: quantity) { result in
        switch result {

        case .success:
            DispatchQueue.main.async {
                self.getUserProducts()

                print(self.cartProducts.first?.quantity ?? 0)

                self.calculateTotalPrice(products: self.cartProducts)
            }
        case .failure(let error):
            print("Error fetching products: (error)")
        }
    }

}

For example, the amount of product in the cart is currently 4. When I add an item to the cart, 4 is printed on the screen and the price is calculated accordingly. Then, when I send a request to Postman, I see that the amount of products in the cart has increased to 5.
I think the problem occurs because the amount of items in the cart is updated exactly and the app calculates the total price before it is captured.

Github repository address of the project: https://github.com/mehmetozkn/swiftui-computer-store-app

2

Answers


  1. Chosen as BEST ANSWER

    I solved the problem by calling the calculateTotal function here

    func getUserProducts() {
            homeService.fetchUserProducts(path: .getProductByUserId) { (result) in
                switch result {
    
                case .success(let cartProducts):
                    if let cartProducts = cartProducts {
                        DispatchQueue.main.async {
                            self.cartProducts = cartProducts
                            self.calculateTotalPrice()
                        }
                       
                    }
    
                case .failure(let error):
                    print("Error fetching user products: (error)")
                }
            }
        }
    

  2. This is an async issue, not a SwiftUI issue.

    Your getUserProducts() function starts a network request, and then returns immediately before it completes. Your addProductToCart() function calls getUserProducts(), and expects the results to be available as soon as getUserProducts() returns. Async code doesn’t work that way.

    I suggest reading up on async await and rewriting your function using async.

    Alternatively you could rewrite your getUserProducts() to take a completion handler.

    Fixing your whole app is more than I’m ready to take on, but here are some steps towards using async:

    Rewrite your getUserProducts function to be async:

        func getProduts() {
            httpDownloader.fetchProducts() { (result) in
                switch result {
    
                case .success(let products):
                    if let products = products {
                        DispatchQueue.main.async {
                            self.products = products
                        }
                    }
    
                case .failure(let error):
                    print("Error fetching products: (error)")
                }
            }
        }
    

    For that to work you’ll also need to rewrite your fetchUserProducts() function to also be async:

        func fetchUserProductsAsync() async throws -> [CartProduct]? {
            guard let url = URL(string: userProductsURL) else {
                throw(NetworkException.notFound)
            }
            let (data, _) = try await URLSession.shared.data(from: url)
            let result = try JSONDecoder().decode([CartProduct].self, from: data)
            return result
        }
    

    And then in your addProductToCart function, you could wrap your call to getUserProducts in a Task:

        func addProductToCart(id: Int, quantity: Int) {
            httpDownloader.addProductToCart(productId: id, quantity: quantity) { result in
                switch result {
    
                case .success:
                    DispatchQueue.main.async {
                        Task {
                            await self.getUserProducts()
                            self.calculateTotalPrice(products: self.cartProducts)
                        }
                    }
                case .failure(let error):
                    print("Error fetching products: (error)")
                }
            }
        }
    

    Edit:

    If instead you want to use completion handlers, a naive rewrite of your getUserProducts() function might look like this:

        func getUserProducts(completion: () -> Void) {
            httpDownloader.fetchUserProducts() { (result) in
                switch result {
    
                case .success(let cartProducts):
                    if let cartProducts = cartProducts {
                        DispatchQueue.main.async {
                            self.cartProducts = cartProducts
    
                        }
                    }
    
                case .failure(let error):
                    print("Error fetching products: (error)")
                }
            }
        }
    

    And then call it like this:

        func addProductToCart(id: Int, quantity: Int) {
            httpDownloader.addProductToCart(productId: id, quantity: quantity) { result in
                switch result {
    
                case .success:
                    DispatchQueue.main.async {
                        self.getUserProducts() {
                            self.calculateTotalPrice(products: self.cartProducts)
                        }
                    }
                case .failure(let error):
                    print("Error fetching products: (error)")
                }
            }
        }
    }
    

    (I say that’s a a naive rewrite because your code puts the results of the network calls into global variables, which is not great practice. It should really be rewritten to pass the resulting array (or an error) to the completion handler, and let the completion handler save the results as needed.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search