Android Test-Driven Development by Tutorials,
Second Edition – Now Updated!

Build testable, sustainable Android apps via JUnit, Mockito, and Espresso
by diving into test-driven development (TDD) in this newly-updated book.

Home iOS & Swift Tutorials

Firebase Tutorial: Getting Started

Learn Firebase fundamentals including saving data, real-time sync, authentication, user status and offline support.

Version

  • Swift 5, iOS 14, Xcode 12
Update note: Lea Marolt Sonnenschein updated this tutorial for iOS 14, Swift 5 and Xcode 12.5. David East wrote the original.

Firebase is a mobile backend-as-a-service that provides powerful features for building mobile apps. Firebase has three core services:

  • Realtime database
  • User authentication
  • Hosting

With the Firebase iOS SDK, you can use these services to create apps without writing any server code!

In this tutorial, you’ll learn the fundamentals of Firebase by making a collaborative grocery list called Grocr. When a user adds items to the list, they instantly appear on everyone’s devices.

But you’re not going to stop there! You’ll tweak Grocr to work offline, so the list stays in sync even with a spotty grocery store data connection.

As you work, you’ll learn how to:

  • Save data to a Firebase database.
  • Sync data in real time.
  • Authenticate users.
  • Monitor online users.
  • Enable offline support.

Getting Started

Download the starter project by clicking Download Materials at the top or bottom of the tutorial.

Grocr contains three view controllers:

  1. LoginViewController.swift: Currently uses hard-coded credentials, but you’ll fix that soon.
  2. GroceryListTableViewController.swift: A subclass of UITableViewController that adds items to a list of local data using a UIAlertController.
  3. OnlineUsersTableViewController.swift: This controller uses Firebase’s presence feature to display all of the users currently online.

In addition to the view controllers, Grocr has two models: GroceryItem.swift and User.swift.

Build and run. You’ll see the app looks like this:

Grocr app screens

There’s a hard-coded user in the app for now. Go ahead, tap Login and play with the app. The app currently works with local data only.

Next, you’ll use Firebase to bring the app to life.

Setting up a Firebase Account

There are three main steps to setting up Firebase in an iOS project:

  1. Create a free Firebase account.
  2. Download and add GoogleService-Info.plist to your app.
  3. Tell Firebase to start when your app launches.

To create a Firebase account, visit the Firebase homepage. Click Get started in the bottom left of the page, and enter the credentials for your Google account, if you aren’t signed in already. If you don’t have a Google account, you’ll need to create one first, which you can do here.

Now you have a clean Firebase console. Don’t worry about forking over any money: you can do everything in this Firebase tutorial with the free plan.

It’s time to create your first project. Click Create a project.

Firebase console

In the dialog that appears, enter Grocr as the Project name and click Continue.

Turn off Enable Google Analytics for this project. Click Create project and wait for it to load. Then, click Continue to get to the project’s dashboard:

Project setup

This is a container for your project’s Firebase services. You’ll use it to store data and authenticate users. Click iOS.

Firebase dashboard

Enter com.raywenderlich.grocr in the iOS Bundle ID field. Click Register app.

Add iOS app

Then click Download GoogleService-Info.plist. Follow the instructions and move it to the Grocr project in Xcode.

Download Google plist

When prompted by Xcode, check Copy Items if needed.

Return to your Firebase project’s webpage. This page describes how to install the Firebase SDK.

Instead of CocoaPods, this project uses Swift Package Manager to manage the SDK. It’s included in the starter project, so click Next.

Install Firebase SDK

The last page explains how to connect Firebase when your app starts. Click Next to finish the setup:

Add Firebase to your app

Finally, click Continue to the console to start coding!

Setup finished

In Xcode, open AppDelegate.swift. Add this code before the return statement within application(_:didFinishLaunchingWithOptions:):

FirebaseApp.configure()

Now you’ll create a connection to Firebase.

Creating a Connection to Firebase

With your Firebase app set up, open GroceryListTableViewController.swift. Add these new properties:

let ref = Database.database().reference(withPath: "grocery-items")
var refObservers: [DatabaseHandle] = []

Using reference(withPath:), you establish a connection to your Firebase database at a given path. In the documentation, Firebase refers to these properties as references because they refer to a location in your Firebase database.

Once you have a reference, you’ll start adding observers to it so that you get notified when a change occurs. It’s best practice to keep track of the references you add, so you can clean them up later. You added refObservers for this purpose. You’ll see this in action a little bit later in this tutorial.

In short, these properties let you save and sync data to the given location.

You’ll notice it doesn’t use the base URL. Instead, it uses a child path grocery-items. The Firebase database is a JSON NoSQL database, so all data stores as JSON.

JSON is a hierarchical tree of key-value data structure. Keys are objects that can contain values that point to other objects.

With Firebase, the key is a URL and the value is arbitrary data that could be a number, string, Boolean or object.

Structuring Data

No matter how it’s formatted on the client, all data stored in Firebase is JSON. Look at the following sample JSON data:

// The root of the tree
{
  // grocery-items
  "grocery-items": {

    // grocery-items/milk
    "milk": {

      // grocery-items/milk/name
      "name": "Milk",

      // grocery-items/milk/addedByUser
      "addedByUser": "David"
    },

    "pizza": {
      "name": "Pizza",
      "addedByUser": "Alice"
    },
  }
}

In the JSON tree above, there’s a path mapped to every piece of data. You can traverse down the tree and retrieve data at deeper locations.

Here, you can retrieve all grocery items by using the path:

grocery-items

To only get the first grocery item, you can navigate to the child path:

grocery-items/milk

Since all Firebase keys map to paths, the key names you choose are especially important.

Understanding Firebase References

A Firebase reference points to a Firebase location where the data is stored. Multiple references share the same connection.

Look at this sample code:

// 1
let rootRef = Database.database().reference()

// 2
let childRef = Database.database().reference(withPath: "grocery-items")

// 3
let itemsRef = rootRef.child("grocery-items")

// 4
let milkRef = itemsRef.child("milk")

// 5
print(rootRef.key)   // prints: nil
print(childRef.key)  // prints: "grocery-items"
print(itemsRef.key)  // prints: "grocery-items"
print(milkRef.key)   // prints: "milk"

Here’s what’s going on:

  1. You create a reference to the root of the Firebase database.
  2. Using a URL, you can create a reference to a child location in your Firebase database.
  3. From the rootRef you can use child(_:) to create a child reference by passing the child path. This reference is the same as the one above.
  4. Using itemsRef, you can create a child reference to the milk location.
  5. Every reference has key. This property tells you what the key name is in the Firebase database.

Adding New Items to the List

At the bottom of GroceryListTableViewController.swift, find addItemDidTouch(_:).

Here, you present the user with a UIAlertController to add a new item.

Inside the method, locate saveAction. Currently, it only saves the data to a local array, so saveAction won’t sync across multiple clients and disappears when you restart the app.

Nobody wants to use an app that doesn’t remember or sync their grocery list! Replace saveAction with:

let saveAction = UIAlertAction(title: "Save", style: .default) { _ in
  // 1
  guard 
    let textField = alert.textFields?.first,
    let text = textField.text,
    let user = self.user 
  else { return }
  
  // 2
  let groceryItem = GroceryItem(
    name: text,
    addedByUser: user.email,
    completed: false)

  // 3
  let groceryItemRef = self.ref.child(text.lowercased())

  // 4
  groceryItemRef.setValue(groceryItem.toAnyObject())
}

Here you:

  1. Get the text field and its text from the alert controller.
  2. Using the current user’s data, you create a new GroceryItem.
  3. Then you create a child reference using child(_:). This reference’s key value is the item’s name in lowercase, so when users add duplicate items, even if they capitalize them, or use mixed case, the database saves only the latest entry.
  4. Use setValue(_:) to save data to the database. This method expects a Dictionary. GroceryItem has a helper method called toAnyObject() to turn it into a Dictionary.

You need to change the database settings too. Go to the Firebase dashboard in your browser. Under Build on the left, select Realtime Database:

Firebase dashboard

Click Create Database on the right:

Realtime database

Select your country and click Next:

Setup country

In the dialog for Security rules for Cloud Firestore, select Start in test mode. By default, the Realtime Database requires user authentication for reading and writing. Click Enable.

Setup mode

You now see your very own Realtime Database:

Realtime database

Select Rules and verify the following in the editor:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

If they don’t match, replace them. Then, click Publish to save your changes.

Database rules

Build and run. In the Firebase dashboard, select Data and position the browser window next to the simulator. When you add an item in the simulator, you’ll see it appear in the dashboard:

Realtime database

Now you have a grocery list app that adds data to Firebase in real time! However, while this key feature works, the table view doesn’t show any of this data.

You’ll get that data to sync from the database to the table view next.

Retrieving Data

You retrieve data in Firebase by attaching an asynchronous listener to a reference using observe(_:with:).

In GroceryListTableViewController.swift, add the following to the end of viewWillAppear(_:):

ref.observe(.value, with: { snapshot in
  print(snapshot.value as Any)
})

This method takes two parameters: an instance of DataEventType and a closure.

The event type specifies what event you want to listen for. The code listens for a .value event type, which reports all types of changes to the data in your Firebase database: added, removed and changed.

When the change occurs, the database updates the app with the most recent data.

The closure notifies the app of a change, passed through as an instance of DataSnapshot. The snapshot, as its name suggests, represents the data at that specific moment in time. To access the data in the snapshot, you use value.

Build and run. You’ll see list items log to the console as they’re added:

Optional({
    pizza = {
        addedByUser = "hungry@person.food";
        completed = 0;
        name = Pizza;
    };
})

Now it’s time to display the grocery list in your table view.

Synchronizing Data to the Table View

In GroceryListTableViewController.swift, replace the previous snippet in viewWillAppear(_:) with:

// 1
let completed = ref.observe(.value) { snapshot in
  // 2
  var newItems: [GroceryItem] = []
  // 3
  for child in snapshot.children {
    // 4
    if
      let snapshot = child as? DataSnapshot,
      let groceryItem = GroceryItem(snapshot: snapshot) {
      newItems.append(groceryItem)
    }
  }
  // 5
  self.items = newItems
  self.tableView.reloadData()
}
// 6
refObservers.append(completed)

Here’s a breakdown:

  1. You attach a listener to receive updates whenever the grocery-items endpoint changes. The database triggers the listener block once for the initial data and again whenever the data changes.
  2. Then, you store the latest version of the data in a local variable inside the listener’s closure.
  3. The listener’s closure returns a snapshot of the latest set of data. The snapshot contains the entire list of grocery items, not just the updates. Using children, you loop through the grocery items.
  4. GroceryItem has an initializer that populates its properties using a DataSnapshot. A snapshot’s value is of type AnyObject and can be a dictionary, array, number or string. After creating an instance of GroceryItem, you add it to the array that contains the latest version of the data.
  5. You replace items with the latest version of the data, then reload the table view so it displays the latest version.
  6. Store a reference to the listener block so you can remove it later.

In viewDidDisappear(_:), add:

refObservers.forEach(ref.removeObserver(withHandle:))
refObservers = []

Here, you iterate over all the observers you’ve previously added and remove them from ref.

Build and run. Add an item, and it’ll show up in the table view.

Realtime data entry

The best part: you don’t need a pull-to-refresh! The list updates in real time!

Real time updates

Removing Items From the Table View

The table view will synchronize on any change to your data. However, there’s nothing to update Firebase when the user decides not to get that pizza.

To notify the database of a deletion, you need to set a Firebase reference to delete an item when the user swipes it away.

Locate tableView(_:commit:forRowAt:). Right now, this method removes a grocery item from the local array using the index path’s row. It works, but there’s a better way.

Replace the existing implementation with:

if editingStyle == .delete {
  let groceryItem = items[indexPath.row]
  groceryItem.ref?.removeValue()
}

Firebase follows a unidirectional data flow model, so the listener in viewWillAppear(_:) notifies the app of the grocery list’s latest value. Removing an item triggers a value change.

You use the index path’s row to retrieve the corresponding grocery item. Each GroceryItem has a Firebase reference property named ref and calling removeValue() on that reference makes the listener you defined in viewWillAppear(_:) fire. The listener has a closure attached that reloads the table view using the latest data.

Build and run. Swipe an item, tap delete and watch it vanish from both your app and in Firebase.

Delete items

Nice work! Your items now delete in real time.

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.

Check item off

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:

  1. You use cellForRow(at:) to find the cell the user tapped.
  2. Then, you get the corresponding GroceryItem by using the index path’s row.
  3. You negate completed on the grocery item to toggle the status.
  4. Then, you call toggleCellCheckbox(_:isCompleted:) to update the visual properties of the cell.
  5. You use updateChildValues(_:), passing a dictionary, to update Firebase. This method is different than setValue(_:) because it only applies updates, whereas setValue(_:) 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.

Check or uncheck items

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.

Order items when checked

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.

Authentication

Select Sign-in method. In Sign-in providers, select the Email/Password row. Toggle Enable and click Save:

Email authentication

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:

  1. Get the email and password as supplied by the user from the alert controller.
  2. Call createUser(withEmail:password:completion:) on the Firebase auth object passing the email, password and completion block.
  3. 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.

New user in Firebase

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:

  1. You create an authentication observer that returns AuthStateDidChangeListenerHandle using addStateDidChangeListener(_:). In the block, you get two parameters: auth and user.
  2. Upon successful authentication, you get a valid, non-nil user; otherwise, the user is nil.
  3. 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(_:).

Setting the User in the Grocery List

Go to GroceryListTableViewController.swift and add a new property:

var handle: AuthStateDidChangeListenerHandle?

You’ll use this handle the same way you did in LoginViewController.

Then, add the following to the bottom of viewWillAppear(_:):

handle = Auth.auth().addStateDidChangeListener { _, user in
  guard let user = user else { return }
  self.user = User(authData: user)
}

Here you attach an authentication observer to the Firebase auth object, which in turn assigns user when a user successfully signs in.

Add the following to viewDidDisappear(_:):

guard let handle = handle else { return }
Auth.auth().removeStateDidChangeListener(handle)

Once again, you remove the observers when you no longer need them.

Build and run. If a user is logged in, the app bypasses LoginViewController and segues to GroceryListTableViewController. When users add items, their email shows in the cell’s detail.

User email shows in list

Since users can log in, they should be able to log out, too.

Logging Users Out

Open OnlineUsersTableViewController.swift and replace the code inside signOutDidTouch(_:) with:

// 1
guard let user = Auth.auth().currentUser else { return }
let onlineRef = Database.database().reference(withPath: "online/\(user.uid)")
// 2
onlineRef.removeValue { error, _ in
  // 3
  if let error = error {
    print("Removing online failed: \(error)")
    return
  }
  // 4
  do {
    try Auth.auth().signOut()
    self.navigationController?.popToRootViewController(animated: true)
  } catch let error {
    print("Auth sign out failed: \(error)")
  }
}

Here’s a code breakdown:

  1. First, you get the currentUser and create onlineRef using its uid, a unique identifier representing the user.
  2. You call removeValue to delete the value for onlineRef. While Firebase automatically adds the user to online upon sign in, it doesn’t remove the user on sign out. Instead, it only removes users when they become disconnected.

    For this app, it doesn’t make sense to show users as online after they log out. So, you manually remove them here.

  3. Within the completion closure, you first check if there’s an error and simply print it if so.
  4. Then you call Auth.auth().signOut() to remove the user’s credentials from the keychain. If there isn’t an error, you dismiss the view controller. Otherwise, you print out the error.

Build and run. Tap the left navigation item. Then tap Sign Out and you’ll return to the login page.

Log out

Success! The app now has basic user authentication.

Next, you’ll monitor users’ online status.

Monitoring Users’ Online Status

It’s time to detect which users are online. Open GroceryListTableViewController.swift and add:

let usersRef = Database.database().reference(withPath: "online")
var usersRefObservers: [DatabaseHandle] = []

The usersRef Firebase reference points to an online location that stores a list of online users. You see the same pattern here with usersRefObservers. It’s a good practice to explicitly keep track of your observers and remove them when you’re done.

Next, add the following to the bottom of addStateDidChangeListener(_:) inside viewWillAppear(_:):

// 1
let currentUserRef = self.usersRef.child(user.uid)
// 2
currentUserRef.setValue(user.email)
// 3
currentUserRef.onDisconnectRemoveValue()

In the code above, you:

  1. Create a child reference using a user’s uid. Firebase generates a unique uid whenever a user creates a new account.
  2. Use this reference to save the current user’s email.
  3. Call onDisconnectRemoveValue() on currentUserRef. This removes the value at the reference’s location after the connection to Firebase closes, like when a user quits your app. This is perfect for monitoring users who have gone offline.

Build and run. When the view loads, the current user’s email is added as a child in the online location.

Realtime logging in on Firebase

Great! Now it’s time to change the bar button item’s number as the user count grows.

Updating the Online User Count

Still in GroceryListTableViewController.swift, add the following code to viewWillAppear(_:):

let users = usersRef.observe(.value) { snapshot in
  if snapshot.exists() {
    self.onlineUserCount.title = snapshot.childrenCount.description
  } else {
    self.onlineUserCount.title = "0"
  }
}
usersRefObservers.append(users)

This code creates an observer that monitors online users. When users go online and offline, the title of onlineUserCount updates with the current user count.

Now, add the following to the bottom of viewDidDisappear(_:):

usersRefObservers.forEach(usersRef.removeObserver(withHandle:))
usersRefObservers = []

This removes associated observers on usersRef.

Next, you’ll display a list of online users.

Displaying a List of Online Users

Open OnlineUsersTableViewController.swift. As you did before, add a local reference to Firebase’s online users record in the class’s properties section:

let usersRef = Database.database().reference(withPath: "online")
var usersRefObservers: [DatabaseHandle] = []

Then, in viewDidLoad, remove:

currentUsers.append("hungry@person.food")

It’s no longer needed.

Next, in viewWillAppear(_:), add:

// 1
let childAdded = usersRef
  .observe(.childAdded) { [weak self] snap in
    // 2
    guard
      let email = snap.value as? String,
      let self = self
    else { return }
    self.currentUsers.append(email)
    // 3
    let row = self.currentUsers.count - 1
    // 4
    let indexPath = IndexPath(row: row, section: 0)
    // 5
    self.tableView.insertRows(at: [indexPath], with: .top)
  }
usersRefObservers.append(childAdded)

Here you:

  1. Create an observer that listens for changes in usersRef, when a new child is added. This is different than observing a value change because it only passes the added child to the closure.
  2. Take the value from the snapshot and append it to the local array.
  3. You’re going to add a new row. The new row index is the count of the local array minus one because the indexes managed by the table view are zero-based.
  4. Create the corresponding NSIndexPath using the calculated row index.
  5. Insert the row using an animation that inserts the cell from the top.

This code renders items as users come online.

Since users can also go offline, the table needs to react to it by removing them. Add the following below the code you just added:

let childRemoved = usersRef
  .observe(.childRemoved) {[weak self] snap in
    guard
      let emailToFind = snap.value as? String,
      let self = self
    else { return }

    for (index, email) in self.currentUsers.enumerated() 
    where email == emailToFind {
      let indexPath = IndexPath(row: index, section: 0)
      self.currentUsers.remove(at: index)
      self.tableView.deleteRows(at: [indexPath], with: .fade)
    }
  }
usersRefObservers.append(childRemoved)

This time, you listen for the removal of children from usersRef. You search the local array, using the email address, to find the corresponding child item. Once located, you delete the associated row from the table.

Next in viewDidDisappear(_:) add:

usersRefObservers.forEach(usersRef.removeObserver(withHandle:))
usersRefObservers = []

This removes relevant observers from usersRef.

Note: There’s a heavy handed method, removeAllObservers() that you can call on a database object to remove all observers. However, keep in mind that removeAllObservers() will remove all observers on a particular node, regardless of where and when in the code they were added.

Build and run.

Tap Online in the Firebase users dashboard. The current user’s email will appear in the table.

With a bit of trickery, you can add a user to Online. Once you do, it shows in the list. Click Remove in the Dashboard and the user fades from existence.

Add user through Firebase

Booyah! The table updates when you add or remove users.

Enabling Offline

Grocery stores are notorious for spotty data connections. You’d think they’d all have Wi-Fi by now, but no!

No problem! You’ll set up your database to work offline.

Open AppDelegate.swift and add the following to the end of application(_:didFinishLaunchingWithOptions:), before return true:

Database.database().isPersistenceEnabled = true

Just like that, your app works offline. Once you make a connection, even offline updates that occur across app restarts will apply to your Firebase database. Oooh-ahhhh!

Where to Go From Here?

You can download the completed project files by clicking Download Materials at the top or bottom of the tutorial.

Note: You still have to add your own GoogleService-Info.plist after downloading the final project.

Throughout this tutorial, you’ve learned the basics of Firebase by building a collaborative grocery list app. Along the way, you implemented:

  • Saving data to a Firebase database.
  • Syncing data in real time.
  • Authenticating users.
  • Monitoring online user status.
  • Enabling offline support.

And you did all this without writing a single line of server code!

To learn more about Firebase, please check out the documentation and the examples provided by Firebase.

If you have any comments or questions about this Firebase tutorial, Firebase or the sample app, please join the forum discussion below!

Add a rating for this content

More like this

Contributors

Comments