Firebase Tutorial: Getting Started

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

4.4 (16) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

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.