Firebase Tutorial: Getting Started
Learn Firebase fundamentals including saving data, real-time sync, authentication, user status and offline support. By Lea Marolt Sonnenschein.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Firebase Tutorial: Getting Started
35 mins
- Getting Started
- Setting up a Firebase Account
- Creating a Connection to Firebase
- Structuring Data
- Understanding Firebase References
- Adding New Items to the List
- Retrieving Data
- Synchronizing Data to the Table View
- Removing Items From the Table View
- Checking Off Items
- Sorting the Grocery List
- Authenticating Users
- Registering Users
- Logging Users In
- Observing Authentication State
- Setting the User in the Grocery List
- Logging Users Out
- Monitoring Users’ Online Status
- Updating the Online User Count
- Displaying a List of Online Users
- Enabling Offline
- Where to Go From Here?
Checking Off Items
You’ve got adding, removing and syncing items covered. That’s all pretty cool, but what about when you’re actually shopping? Should you delete stuff you’ve got, or would it be better to mark things off as you add them to your basket?
Back in the analog days of pens and paper, people used to cross stuff off the grocery list. You’ll mimic that familiar behavior in this app, but with a modern twist!
When tapped, items turn gray and show a checkmark to give the user visual feedback that the item is no longer needed.
Open GroceryListTableViewController.swift and find toggleCellCheckbox(_:isCompleted:)
. This method toggles the necessary view properties for UITableViewCell
, depending on whether its associated item is complete.
It’s called from tableView(_:cellForRowAtIndexPath:)
, when the table view first loads, and from tableView(_:didSelectRowAt:)
, when the user taps a row.
Replace the current implementation of tableView(_:didSelectRowAt:)
with:
// 1
guard let cell = tableView.cellForRow(at: indexPath) else { return }
// 2
let groceryItem = items[indexPath.row]
// 3
let toggledCompletion = !groceryItem.completed
// 4
toggleCellCheckbox(cell, isCompleted: toggledCompletion)
// 5
groceryItem.ref?.updateChildValues([
"completed": toggledCompletion
])
Here’s the play-by-play:
- You use
cellForRow(at:)
to find the cell the user tapped. - Then, you get the corresponding
GroceryItem
by using the index path’s row. - You negate
completed
on the grocery item to toggle the status. - Then, you call
toggleCellCheckbox(_:isCompleted:)
to update the visual properties of the cell. - You use
updateChildValues(_:)
, passing a dictionary, to update Firebase. This method is different thansetValue(_:)
because it only applies updates, whereassetValue(_:)
is destructive and replaces the entire value at that reference.
Build and run. Tap an item and see that it toggles back and forth between the complete and incomplete statuses.
Congratulations, you’ve got yourself a pretty sweet grocery list app!
Sorting the Grocery List
You know how sometimes you forget to pick up ice cream because it’s nestled between a couple of things you’ve already marked off, and your eyes play tricks on you? Well, you, dear reader, can fix that.
The app would be even more awesome if checked items automatically moved themselves to the bottom of the list. Then the remaining items would be clear and easy for your eyes to see.
Using Firebase queries, you can sort the list by arbitrary properties. Still in GroceryListTableViewController.swift, replace the observer in viewWillAppear(_:)
as follows:
let completed = ref
.queryOrdered(byChild: "completed")
.observe(.value) { snapshot in
var newItems: [GroceryItem] = []
for child in snapshot.children {
if
let snapshot = child as? DataSnapshot,
let groceryItem = GroceryItem(snapshot: snapshot) {
newItems.append(groceryItem)
}
}
self.items = newItems
self.tableView.reloadData()
}
To order the data by the completed
value you call queryOrdered(byChild:)
on the Firebase reference, which takes a key to order by.
Since you want the list ordered by completion, you pass the completed
key to the query. Then, queryOrdered(byChild:)
returns a reference that asks the server to return data in an ordered fashion.
Build and run. Tap a row to toggle its completion status. The completed items magically move to the bottom of the list.
Wow! You’re really making grocery shopping easier. It seems like it should be simple enough to sync the data across multiple users. For example, users might want to share their list with a significant other or housemate.
This sounds like a job for authentication!
Authenticating Users
Firebase has an authentication service that lets apps authenticate through several providers. You can authenticate users with Google, Twitter, Facebook, GitHub, email and password, anonymous and even custom back ends. Here, you’ll use email and password because it’s the easiest to set up.
To enable email and password authentication, go to the Firebase dashboard. Click Authentication and then Get started.
Select Sign-in method. In Sign-in providers, select the Email/Password row. Toggle Enable and click Save:
Now you’re ready to authenticate users with their email and password!
Registering Users
In LoginViewController.swift, find signUpDidTouch(_:)
. Replace the existing implementation with:
// 1
guard
let email = enterEmail.text,
let password = enterPassword.text,
!email.isEmpty,
!password.isEmpty
else { return }
// 2
Auth.auth().createUser(withEmail: email, password: password) { _, error in
// 3
if error == nil {
Auth.auth().signIn(withEmail: email, password: password)
} else {
print("Error in createUser: \(error?.localizedDescription ?? "")")
}
}
In this code, you:
- Get the email and password as supplied by the user from the alert controller.
- Call
createUser(withEmail:password:completion:)
on the Firebase auth object passing the email, password and completion block. - If there are no errors, Firebase created the user account. However, you still need to authenticate this new user, so you call
signIn(withEmail:password:)
, again passing in the supplied email and password.
Build and run. Enter your email and password, then tap Sign up. The view controller won’t navigate to anything on successful login just yet.
Refresh the Firebase Login & Auth tab to see the newly created user.
Hooray! The app now registers users and lets them log in.
Don’t celebrate yet, though. You need to finish the process so people can use the app as intended.
Logging Users In
The Sign up button can register and log in users. However, because it doesn’t perform an authentication, the Login button effectively does nothing.
Still in LoginViewController.swift, find loginDidTouch(_:)
and replace its implementation with:
guard
let email = enterEmail.text,
let password = enterPassword.text,
!email.isEmpty,
!password.isEmpty
else { return }
Auth.auth().signIn(withEmail: email, password: password) { user, error in
if let error = error, user == nil {
let alert = UIAlertController(
title: "Sign In Failed",
message: error.localizedDescription,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true, completion: nil)
}
}
This code authenticates users when they attempt to log in by tapping Login.
Now you only need to perform the segue to the next controller when the user has logged in.
Observing Authentication State
Firebase has observers that let you monitor a user’s authentication state. This is a great place to perform a segue.
Add a new property to LoginViewController
:
var handle: AuthStateDidChangeListenerHandle?
You attach this handle to the observer.
Then, add the following at the end of viewWillAppear(_:)
:
// 1
handle = Auth.auth().addStateDidChangeListener { _, user in
// 2
if user == nil {
self.navigationController?.popToRootViewController(animated: true)
} else {
// 3
self.performSegue(withIdentifier: self.loginToList, sender: nil)
self.enterEmail.text = nil
self.enterPassword.text = nil
}
}
Here’s a run-down of what’s happening:
- You create an authentication observer that returns
AuthStateDidChangeListenerHandle
usingaddStateDidChangeListener(_:)
. In the block, you get two parameters:auth
anduser
. - Upon successful authentication, you get a valid, non-nil
user
; otherwise, the user isnil
. - If you have a valid
user
, you perform the segue and clear the text from the text fields. It may seem strange that you don’t pass the user to the next controller, but you’ll see how to get this within GroceryListTableViewController.swift.
Add the following at the end of viewDidDisappear(_:)
:
guard let handle = handle else { return }
Auth.auth().removeStateDidChangeListener(handle)
This removes the listener by passing the AuthStateDidChangeListenerHandle
in removeStateDidChangeListener(_:)
.