christostsang
christostsang

Reputation: 1841

Working with asynchronous data with Swift closures and Firebase

I need to call a specific Firebase reference and get back data. This operation will take place inside multiple VCs so I want to have a class where I will have various functions calling Firebase. For example, if I want to get all articles I will call my FirebaseHelpers class, and use the method/closure fetchArticles(). This way, if I want to refactor something I will only do it in FirebaseHelpers class, and not go through all VCs.

FirebaseHelpers

import UIKit
import Firebase

class FirebaseHelpers {

    func fetchArticles(completion: @escaping ([Article]?, Error?) -> Void) {
            
        var articles = [Article]()

        let articlesQuery = Database.database().reference().child("articles").queryOrdered(byChild: "createdAt")

        articlesQuery.observe(.value, with: { (snapshot) in

            guard let articlesDictionaries = snapshot.value as? [String : Any] else { return }
        
            articlesDictionaries.forEach({ (key, value) in
            
                guard let articleDictionary = value as? [String: Any] else { return }

                // build articles array
                let article = Article(dictionary: articleDictionary)

                print("this is article within closure \(article)")
            
                articles.append(article)
            
            })
        
        })

        completion(articles, nil)
    }
}

In any viewController

let firebaseHelpers = FirebaseHelpers()

var articles = [Article]() {
    didSet {
        self.collectionView.reloadData()
    }
}

// this is inside viewDidLoad()
firebaseHelpers.fetchArticles { (articles, error) in
        
    guard let articles = articles else { return }
        
    print("articles \(articles)")
        
    self.articles = articles
}

The problem is that I don't get any results back. In my VC the print("articles (articles)") will return an empty array. But in my FirebaseHelpers fetchArticles() the print("this is article within closure (article)") will print the article(s) just fine.

Any idea why this is happening?

Thanks in advance.

Upvotes: 2

Views: 222

Answers (1)

pawello2222
pawello2222

Reputation: 54516

You can move completion inside your asynchronous function:

class FirebaseHelpers {

    func fetchArticles(completion: @escaping ([Article]?, Error?) -> Void) {
            
        var articles = [Article]()

        let articlesQuery = Database.database().reference().child("articles").queryOrdered(byChild: "createdAt")

        articlesQuery.observe(.value, with: { (snapshot) in

            guard let articlesDictionaries = snapshot.value as? [String : Any] else { return }
        
            articlesDictionaries.forEach({ (key, value) in
            
                guard let articleDictionary = value as? [String: Any] else { return }

                // build articles array
                let article = Article(dictionary: articleDictionary)

                print("this is article within closure \(article)")
            
                articles.append(article)
            })
        
            completion(articles, nil) // <- move here
        })

        // completion(articles, nil) // <- remove
    }
}

Otherwise completion will be called before your asynchronous function.

Upvotes: 3

Related Questions