Search code examples
swiftuiclosuresalamofire

Problems with Alamofire and Swift closures


I have the following class with a function that returns a boolean value using alamofire.

class Productos: Codable{
    
    // MARK: Propiedades de los productos
    var id_producto : String = ""
    var producto_es : String = ""
    var id_seccion : String = ""
   
    init(id_producto : String = "", producto_es : String = "", id_seccion : String = ""){
        self.id_producto = id_producto
        self.producto_es = producto_es
        self.id_seccion = id_seccion
    }

    func hayProductosNuevos(ultimoProductoEnBd: Int, completion: @escaping (Bool) -> Void) {
        let urlWS = Globales().urlWS//Url para la webservice
      
        AF.request(urlWS+"actualiza_productos.php?ultimo_producto=\(ultimoProductoEnBd)",method: .get).responseDecodable(of: [Productos].self) { responseConsulta in
            switch responseConsulta.result{
                case .success(let actualizacionDeProductos):
                    if (actualizacionDeProductos.isEmpty){
                        completion(false)
                    } else {
                        completion(true)
                    }
                case .failure(let error):
                    print("hay un error \(String(describing: error.errorDescription))")
            }
         }
    }
}

My problem is how to call it and control what is returned to me from my view:

enter image description here

I know that within a view I cannot call it that. The idea is if I return true, perform a product update and display a ProgressView.

I have tried everything I have been reading, using its init() in the view. But I get another error.

enter image description here

I think my problem is that from Alamofire I need to know if I have to update my application's data or not, that's why it returns true or false, but I'm not clear about the use of closures, so I don't know how I have to call Alamofire from my view so I can control the data.


Solution

  • This approach is wrong, you cannot put the model which is used in an array containing multiple instances and the controller to load the data in the same object. Well actually you can do it but then you have to create static APIs which is bad practice though.

    First of all declare the object representing a product as struct named in singular form and without the logic to load the data.

    struct Producto: Decodable {
        
        // MARK: Propiedades de los productos
        var id_producto : String 
        var producto_es : String
        var id_seccion : String
    }
    

    And it's highly recommended to use camelCase struct member names, but this is beyond the question. And if the values will never change declare the struct members as constants (let).


    Then create a class conforming to ObservableObject for the logic to load the data with a @Published property for the products and a method to load the data. A completion handler is not needed, assign (or append) the result to the array. Actually AF is not needed either for a simple GET request, built-in URLSession and JSONDecoder can do the same.

    @MainActor
    class ViewModel : ObservableObject {
        @Published var productos = [Producto]()
    
        func hayProductosNuevos(ultimoProductoEnBd: Int) {
            let urlWS = Globales().urlWS //Url para la webservice
          
            AF.request(urlWS+"actualiza_productos.php?ultimo_producto=\(ultimoProductoEnBd)",method: .get).responseDecodable(of: [Producto].self) { responseConsulta in
                switch responseConsulta.result {
                    case .success(let actualizacionDeProductos):
                        self.productos = actualizacionDeProductos
                        // or self.productos.append(contentsOf: actualizacionDeProductos)
                    case .failure(let error):
                        print("hay un error", error)
                }
             }
        }
    }
    

    In the view create an instance of ViewModel as @StateObject and load the data in onAppear

    struct ActualizarProductos : View {
        @StateObject private var viewModel = ViewModel()
        @State private var ultimoProductoEnBd = 0
    
        var body: some View {
            NavigationView {
              // UI stuff
            }
            .onAppear {
                viewModel.hayProductosNuevos(ultimoProductoEnBd: ultimoProductoEnBd)
            }
        }
    }
    

    The ultimoProductoEnBd is not handled in the question, it's just an example adding a @State property.