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:
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.
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.
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.