Understanding the Firebase Observe Function

Patrick O'Leary
4 min readApr 15, 2017

I’ve been working with a team on a fitness app. The app will allow you to start or join a team, allow you to create fitness challenges for your team, and then track your progress through those challenges. We’re using Firebase to store all our database information, and I’ve been the primary person coding our Firebase manager.

I think the coolest thing about Firebase is in how you retrieve data from your database. Normally, with an API you set up a URLRequest with an attached ‘GET’ method, and get back a single instance of whatever data you were requesting. In Firebase, you don’t just send a request and get back a single response. Oh no. In Firebase, you’re attaching a listener to an endpoint in your database. This listener is observing that end point, and reporting back anytime the data within the endpoint changes.

We’re listening…

Here’s how Google states it in their doc:

Firebase data is written to a FIRDatabase reference and retrieved by attaching an asynchronous listener to the reference. The listener is triggered once for the initial state of the data and again anytime the data changes.

I’d like to say that I’m bringing this subject up because I have some awesome cool new trick to show. But why I’m really talking about Firebase is because I did something super boneheaded in my code. That’s kind of what a lot of coding is though, right? You try out some, it doesn’t work, and later when you figure out why, you go “oh, right, that was stupid.”

So hopefully you can gain some insight from my mistakes. My FirebaseManager class has a function:

static func fetchAllTeams(completion: @escaping ([Team]) -> Void) { //fetches all teams and returns them in an array through a completion
var teams = [Team]()
dataRef.child("teams").observe(.value, with: { (snapshot) in
let teamDict = snapshot.value as? [String: Any]
if let teamDict = teamDict {
for team in teamDict {
let teamValues = team.value as? [String: Any] ?? [:]
let team = Team(id: team.key, dict: teamValues)
teams.append(team)
}
}
completion(teams)
})
}

Pretty cool. Then in my ‘TeamsViewController’ I have another function (called “get team data” or something) that calls on this function and uses it to populate the arrays that hold data for a UITableView.

private func getAllTeams(user: User, completion: @escaping () -> Void) { //Get all teams that exist in the data base, sort them alphabetically and then set them equal to the allTeams array available to TeamsVC
FirebaseManager.fetchAllTeams { (teams) in
self.myTeams.removeAll()
self.publicTeams.removeAll()
self.filteredTeams.removeAll()
for team in teams {
if let teamID = team.id {
if user.teamIDs.contains(teamID) {
print("APPEND MY TEAMS")
self.myTeams.append(team)
} else {
print("APPEND Public TEAMS")
self.publicTeams.append(team)
}
}
}
self.myTeams = self.myTeams.sorted {$0.name.lowercased()
$1.name.lowercased()}
self.publicTeams = self.publicTeams.sorted {$0.name.lowercased() < $1.name.lowercased()}
self.filteredTeams = self.publicTeams
completion()
}
}

What’s great, is because this is set up as an ‘observer’ in Firebase, if I add teams to the database in another view, I don’t have to call on this function again. My firebase observer will see that the data has changed, then call on the closure I defined in the .observe function. Remember this, because it’s where I screwed up in the next part: When you set an observer, if it sees any changes in the data it’s observing, it will call on the closure — which is a function that you’ve defined.

So I have another UIViewController that I’m calling TeamDetailVC. This displays the challenges and team members within a team. The problem is that when I add a new challenge to the team, my ChallengesTableView is duplicating the new challenges. Here’s my FirebaseManager function:

static func fetchChallenge(withChallengeID challengeID: String, completion: @escaping (Challenge) -> Void) {
dataRef.child("challenges").child(challengeID).observe(.value, with: {(snapshot) in
if let challengeDict = snapshot.value as? [String: Any] {
let challenge = Challenge(id: challengeID, dict:
challengeDict)
completion(challenge)
} else {
print("not in completion")
}
})
}

And here’s how I call that function in the TableView:

func getTeamChallenges(forTeam team: Team?, completion: @escaping () -> Void) {
if let challengeList = team?.challengeIDs {
self.teamChallenges.removeAll()
for (index, challengeID) in challengeList.enumerated() {
FirebaseManager.fetchChallenge(withChallengeID: challengeID, completion: { (challenge) in
self.teamChallenges.append(challenge)
completion()
})
}
}
}

Do you see the problem here? When I add a challenge to the database, my ‘observer’ in FirebaseManager sees the change, so it will fire off the function that I’ve created for it in the .observe closure. My closure then gets the data, creates an instance of the Challenge class, and sends it out through an escaping closure. When THIS escape closure is called in my getTeamChallenges method, the only thing that’s happening is that my teamChallenges array is being appended with the new Challenge. The teamChallenges.removeAll() is never called. None of the loop I’ve set up will be recalled. The tableView data is reloaded elsewhere in the completion, with the array that had been previously populated, only this time with an extra “new challenge” at the end of it.

So how to fix this? Well, Firebase has another function called .observeSingleEvent. This works more like how a URLRequest would work, where we just grab a single instance of the data. Then, I would just have to make sure that my “getTeamChallenges” function is called every time the TeamDetailVC will appear. Alternatively, I could alter my FirebaseManager function so that it takes in an array of “challengeIDs,” and then returns the array of challenges by itself. I think I like the second option better, although it will certainly be more involved. I’ll post my solution once I’ve got it to work! (Here it is)

--

--