Swift and Firebase — Datasource Duplication?
A soundproof method to listen and observe with confidence.
Prerequisites
A basic understanding of Swift and Firebase by Google (Realtime Database).
What’s this for?
Loading any type of JSON data from the Realtime Database whether it be loading a social feed or pulling down a list of users. The key element here is observing and automatically updating with 100% accuracy. In addition, we’ll call a completion handler when the data has completed loading. Let’s get started!
Jumping in
Amongst all the options for choosing a Backend, you decide on Firebase by Google, Realtime Database. Got it. You start filling your UICollection and UITableViews with great content and bam, the travesty of duplication happens. You’re removing listeners and clearing the datasource arrays. What’s the issue!? Let’s fix that with a full proof method to observe and listen without fear.
Observing Overview
Step 1: .observe(.valueStep 2: .observeSingleEvent(of: .valueStep 3: .observe(.childAdded
Implementation
Let’s make a new class and add some variables above the viewDidLoad function and import Firebase.
let databaseRef = Database.database().reference()var observingRefOne = Database.database().reference()var handleOne = DatabaseHandle()var compileCounter : Int = 0
Function
Let’s make a function that escapes when complete called fetchCurrentChatMessages.
func fetchCurrentChatMessages(completion : @escaping (_ isComplete : Bool)->()) {}
Step 1: Let’s check for authorization and add the observer. This will listen for any changes at the paths end and fire off the function again. Make sure you use your own path here.
func fetchCurrentChatMessages(completion : @escaping (_ isComplete : Bool)->()) {guard let user_uid = Auth.auth().currentUser?.uid else {return}let ref = self.databaseRef.child(“messages”).child(user_uid)ref.observe(.value, with: { (snapListener : DataSnapshot) in}) { (error) incompletion(false)}}
Step 2: Let’s add the compileCounter variable to see how many objects we’re going to loop through and make sure that data DOES exist. If not, we’ll call the failure state later on.
func fetchCurrentChatMessages(completion : @escaping (_ isComplete : Bool)->()) {guard let user_uid = Auth.auth().currentUser?.uid else {return}let ref = self.databaseRef.child(“messages”).child(user_uid)ref.observe(.value, with: { (snapListener : DataSnapshot) inref.observeSingleEvent(of: .value, with: { (snapCount : DataSnapshot) inif snapCount.exists() {let snapChildrenCount = snapCount.childrenCount} else if !snapCount.exists() {completion(false)}}) { (error) incompletion(false)}}) { (error) incompletion(false)}}
Step 3: Now for the tricky part. Let’s add an observer that loops through all existing objects and connect our handle while increasing our compileCounter variable by 1.
func fetchCurrentChatMessages(completion : @escaping (_ isComplete : Bool)->()) {guard let user_uid = Auth.auth().currentUser?.uid else {return}let ref = self.databaseRef.child(“messages”).child(user_uid)ref.observe(.value, with: { (snapListener : DataSnapshot) inref.observeSingleEvent(of: .value, with: { (snapCount : DataSnapshot) inif snapCount.exists() {let snapChildrenCount = snapCount.childrenCountself.observingRefOne = self.databaseRef.child(“messages”).child(user_uid)self.handleOne = self.observingRefOne.observe(.childAdded, with: { (snapLoop : DataSnapshot) inself.compileCounter += 1guard let json = snapLoop.value as? [String : Any] else {return}//APPEND YOUR OBJECT DATA AND ORGANIZE/FILTER AS NEEDEDif self.compileCounter == snapChildrenCount {completion(true)}}, withCancel: { (error) incompletion(false)})} else if !snapCount.exists() {completion(false)}}) { (error) incompletion(false)}}) { (error) incompletion(false)}}
Once our compileCounter variable matches our snapChildrenCount constant, we call the completion handler because our data has been loaded! Now we need to remove our observer. Our completion handler should call two functions depending on its completion state. First, make sure you are calling the fetchCurrentChatMessages function in the viewDidLoad or put it in a new function to keep the code clean.
self.fetchCurrentChatMessages { (isComplete) inif isComplete == true {self.handleSuccess()} else if isComplete == false {self.handleFailure()}}
Now, add the handleSuccess and handleFailure functions.
func handleSuccess() {self.compileCounter = 0self.observingRefOne.removeObserver(withHandle: self.handleOne)DispatchQueue.main.async {//RELOAD YOUR COLLECTION OR TABLE VIEW}}func handleFailure() {self.compileCounter = 0self.messageArray.removeAll() // REPLACE THIS WITH YOUR DATASOURCEself.observingRefOne.removeObserver(withHandle: self.handleOne)DispatchQueue.main.async {//RELOAD YOUR COLLECTION OR TABLE VIEW}}
Lastly, make sure you are clearing the datasource array and resetting the compileCounter variable after the observer gets called again. The final fetchCurrentChatMessages function should look like this.
func fetchCurrentChatMessages(completion : @escaping (_ isComplete : Bool)->()) {guard let user_uid = Auth.auth().currentUser?.uid else {return}let ref = self.databaseRef.child(“messages”).child(user_uid)ref.observe(.value, with: { (snapListener : DataSnapshot) inself.compileCounter = 0self.messageArray.removeAll() // REPLACE THIS WITH YOUR DATASOURCEref.observeSingleEvent(of: .value, with: { (snapCount : DataSnapshot) inif snapCount.exists() {let snapChildrenCount = snapCount.childrenCountself.observingRefOne = self.databaseRef.child(“messages”).child(user_uid)self.handleOne = self.observingRefOne.observe(.childAdded, with: { (snapLoop : DataSnapshot) inself.compileCounter += 1guard let json = snapLoop.value as? [String : Any] else {return}//APPEND YOUR OBJECT DATA AND ORGANIZE/FILTER AS NEEDEDif self.compileCounter == snapChildrenCount {completion(true)}}, withCancel: { (error) incompletion(false)})} else if !snapCount.exists() {completion(false)}}) { (error) incompletion(false)}}) { (error) incompletion(false)}}
That’s it! You can now listen/observe and automatically update your UICollection and UITableViews without the travesty of data duplication! Feel free to copy and paste as needed!