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 3 of 5 of this article. Click here to view the first page.

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(_:).