How to have a Swift file that runs DB requests and return variables from views

45 Views Asked by At

I am trying to create a class that it runs a Firebase requests and saves the result in variables. Then from different views I access the variables. Each variable will be for one category.

I have made a class in the same view as I am showing the data in the app, and it is working. But when I move it to a separate swift file, it gives the following error:

struct ContentView: View {
    var body: some View {
        Home()

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


struct Home : View {
    @ObservedObject var categories = getCategoriesData()

    var body: some View { 
// Error: Unable to infer complex closure return type; add explicit type to disambiguate
        VStack {
            List(categories.datas) { i in
                Text(i.name)
            }
        }

    }
}

Swift file:

class getCategoriesData : ObservableObject {
    @Published var datas = [category]()

    init () {
        let db = Firestore.firestore()

        db.collection("ingredients").addSnapshotListener{ (snap, err) in

            if err != nil {
                print((err?.localizedDescription)!)
                return
            }
            for i in snap!.documentChanges {
                let id = i.document.documentID
                let name = i.document.get("name") as! String

                self.datas.append(category(id: id, name: name))
            }
        }


    }
}

struct category : Identifiable {
    var id : String
    var name : String


}

Just to be clear, right now I am getting data from ingredients category in Firebase, I want to rerun the class and store each category result in an array/map datas in this case so then I can access them in different views. OR have it in a different class so for each view I run the DB request and get data for one of the categories.

Any idea how to make this working?

Thanks

1

There are 1 best solutions below

0
On

I was able to compile your code, but the preview pane said it wasn't able to render the view because the compiler crashed.

The main problem with your code seems to be that the data access code is in your initialiser. I'd not recommend doing this. Instead, extract this piece of code into a method fetchData and call that once your view appears. For example like this:

import SwiftUI
import Firebase

struct ContentView: View {
  var body: some View {
    Home()
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}


struct Home : View {
  @ObservedObject var viewModel = CategoryViewModel()

  var body: some View {
    VStack {
      List(viewModel.categories) { category in
        Text(category.name)
      }
      .onAppear() {
        self.viewModel.fetchData()
      }
    }
  }
}
import Foundation
import FirebaseFirestore

struct Category : Identifiable {
  var id : String
  var name : String
}

class CategoryViewModel: ObservableObject {
  @Published var categories = [Category]()

  func fetchData() {
    let db = Firestore.firestore()

    db.collection("ingredients").addSnapshotListener{ (snapshot, err) in
      guard let documents = snapshot?.documents else {
        print("No documents")
        return
      }
      self.categories = documents.map { documentSnapshot in
        let id = documentSnapshot.documentID
        let name = documentSnapshot.get("name") as! String
        return Category(id: id, name: name)
      }
    }
  }
}

You will notice I also cleaned up the naming (I'd recommend reading the Ray Wenderlich and Google styleguides for Swift), and converted your mapping code to a map - this makes things easier to read and a bit safer as well.

As for your question about how to subscribe to more than just one collection, I'd recommend using a dedicated view model for each semantically meaningful view.