kirkyoyx
kirkyoyx

Reputation: 373

SwiftUI and MVVM: How to display data from Firebase/Firestore in View?

I'm fetching some data from the Firebase database and would like to display them in a View. The fetching part works so far, but not displaying the data. Since I'm new to the MVVM pattern I'm a little bit confused where my mistake is. I think I'm missing something about the async calls, but I'm not sure.

For fetching the data I created a FirestoreManager:

class FirebaseManager {
private let db = Firestore.firestore()

func getUploadedDecks() -> [FlashcardDeck]{
    var uploadedDecks = [FlashcardDeck]()
    
    db.collection(K.FSCollectionName).getDocuments { (querySnapshot, error) in
        guard let documents = querySnapshot?.documents else {
            fatalError("error downloading documents")
        }
        
        for document in documents{
            if let deck = try? document.data(as: FlashcardDeck.self) {
                uploadedDecks.append(deck)
                print(deck.title) // <----- prints the ID correctly
            }
        }
    }
    return uploadedDecks
}

ViewModel:

class DownloadViewModel: ObservableObject {

@Published var decks = [FlashcardDeck]()
@Published var show = false

func fetchDecks(){
    self.decks = FirebaseManager().getUploadedDecks()
}

}

View: Here appears only the "loading..." Textfield.

struct DownloadDecksView: View {
@ObservedObject var viewModel: DownloadViewModel

var body: some View {
    VStack{
        if viewModel.decks == [] {
            Text("loading...")
        } else { 
            ForEach(viewModel.decks) { elem in 
                Flashcard(elem)
        }
    }
    .onAppear {
        viewModel.fetchDecks()
    }
}

    

MainView:

struct HomeView: View {
    @StateObject var downloadViewModel = DownloadViewModel()
    ...

    var body: some View {
        DownloadDecksView(viewModel: downloadViewModel)
        ...
    }
}

Upvotes: 2

Views: 801

Answers (1)

Asperi
Asperi

Reputation: 257573

This is all about asynchronous retrieving, so you need to return results in callback, like

class FirebaseManager {
private let db = Firestore.firestore()

func getUploadedDecks(completion: @escaping ([FlashcardDeck]) -> ()) {
    
    db.collection(K.FSCollectionName).getDocuments { (querySnapshot, error) in
        guard let documents = querySnapshot?.documents else {
            fatalError("error downloading documents")
        }
        
       var uploadedDecks = [FlashcardDeck]()
        for document in documents{
            if let deck = try? document.data(as: FlashcardDeck.self) {
                uploadedDecks.append(deck)
            }
        }
        completion(uploadedDecks)       // << here !!
    }
    // no return !!
}

and use it as

class DownloadViewModel: ObservableObject {

@Published var decks = [FlashcardDeck]()
@Published var show = false

  func fetchDecks(){
    FirebaseManager().getUploadedDecks() { [weak self] decks in
       DispatchQueue.main.async {
          // always update UI-linked properties on main queue
          self?.decks = decks
       }
    }
  }
}

Upvotes: 2

Related Questions