0

I'm developing an iOS app with Firebase. My root view controller is a UITabBarController.

All of the controllers within said TabBar need to listen to a User document in Firebase's Firestore, but creating a listener in each one doesn't seem to be the most efficient way to do it.

I'm aware I could create the listener in a CustomTabBarController like so:

import UIKit
import FirebaseFirestore

class CustomTabBarController: UITabBarController {

    var userListener: ListenerRegistration?
    var user: User? // User is a custom class

    override func viewDidAppear(_ animated: Bool) {
        let userID = UserDefaults.standard.value(forKey: "id") as! String

        userListener = Firestore.firestore().document("users/\(userID)").addSnapshotListener { (document, error) in
            if let document = document {
                self.user = User(firebaseDocument: document)
            } else {
                // handle error!
            }
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        if let listener = userListener {
            listener.remove()
        }
    }

}

and then access that user property from all ChildViewController's like so:

class ChildViewController: UIViewController {

    var user: User?

    override func viewDidLoad() {
        super.viewDidLoad()

        let customTabBarController = tabBarController as! CustomTabBarController
        self.user = customTabBarController.user
    }
}

I find two problems with this approach:

  1. The user property in ChildViewController will not update, as it is only set in the viewDidLoad method.
  2. I don't think CustomTabBarController is the place to handle the listener failure. Different ChildViewController's will have different ways to deal with it.

Questions: What are my options to create a listener capable of updating any UIVIewController's that need its result? Am I on the right path here or maybe I can use the AppDelegate or even a Singleton to achieve this.

Thanks!

Daniel Valderrama
  • 321
  • 1
  • 3
  • 13

2 Answers2

1

From your current class you can access all the vcs with

self.viewControllers 

and apply the needed updates when you get a new user update by casting , or listen to user with

var ob:NSKeyValueObservation! 

// in viewDidLoad
let tab = self.tabBarcontroller as! CustomTabBarController
ob = tab.observe(\.user, options: [.new]) { (tab, change) in
 //
}

Inside every child , or you can use NotificationCenter for 1-M observation , for separation purposes think of the user like

 class Service {

    static let shared = Service()
    var userListener: ListenerRegistration?
    var user: User? // User is a custom class

    func listenToUpdates() {
        let userID = UserDefaults.standard.value(forKey: "id") as! String

        userListener = Firestore.firestore().document("users/\(userID)").addSnapshotListener { (document, error) in
            if let document = document {
                self.user = User(firebaseDocument: document)
            } else {
                // handle error!
            }
        }
    }

 }

Then

 Service.shared.listenToUpdates()
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
0

You can create a service class which will handle your queries to Firestore. If you then want to pass the result to your view controller just call a closure. You can do something like this:

class FirestoreApiService {
    func getFirebaseUser(withId id: String, then completion: @escaping ((Result<User>) -> Void)) {
        Firestore.firestore().document("users/\(userID)").addSnapshotListener { (document, error) in
            if let document = document {
                completion(.success(User(firebaseDocument: document))
            } else {
                completion(.failure(error))
            }
        }
    }
}

Where Result is just a generic result enum:

enum Result<Value> {
    case success(Value)
    case failure(Error)
}

Then in your ChildViewController call the method:

let apiService = FirestoreApiService()
apiService.getFirebaseUser(withId: "12345") { result in
    switch result {
        case .success(let user):
        //handle success
        case .error(let error):
        //handle error
    }
}

Keep in mind this is just pseudo code and it is not tested.

Vasil Garov
  • 4,851
  • 1
  • 26
  • 37