Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Firebase Tutorial: Getting Started

$
0
0

firebase-discs

Firebase is a mobile-backend-as-a-service that provides several features for building powerful mobile apps. Firebase has three core services: a realtime database, user authentication and hosting. With the Firebase iOS SDK, you can use these services to build powerful apps without writing a single line of server code.

The realtime database is one of the most unique features of Firebase.

Ever used pull-to-refresh to fetch new data? You can forget about it with Firebase.

When a Firebase database updates, all connected users receive updates in realtime. This means your app can stay up to date without user interaction.

In this Firebase tutorial, you’ll learn all the fundamentals of Firebase by making a collaborative grocery list app called Grocr. When items get added to the list they’ll appear instantly on any users’ devices. But you’re not going to stop there, no, 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 about:

  • Saving data to a Firebase database
  • Syncing data from Firebase a database in realtime
  • Authenticating users
  • Monitoring online users
  • Enabling offline support

Get ready to realtime all the things!

Getting Started

Download the Grocr starter project. It uses CocoaPods to manage the Firebase dependency and contains a basic working project.

Open the Grocr.xcworkspace in Xcode. This project contains three view controllers:

  1. The first is the LoginViewController.swift. Right now the login is using hard coded user credentials, but you’ll fix that soon.
  2. Second is the GroceryListTableViewController.swift. This view is a subclass of UITableViewController that adds items to a list of local data using a UIAlertController.
  3. Last is the OnlineUsersTableViewController.swift. This view will use Firebase’s presence feature to display all of the users currently online.

In addition to the view controllers, there are two models: GroceryItem.swift and User.swift. These are just two structs that serve as models for your data.

Build and run, and you’ll see the app looks like this:

Grocr-Starter

If you play around, you’ll see that it only works with local data. You can log in just by tapping Login using the hard-coded user. Next, you’ll use Firebase to bring the app to life.

Setting up a Firebase Account

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

  1. Create a free Firebase account;
  2. Get the URL of your first app.

To create an account, visit the sign up page. After signing up, you’ll have a shiny new Firebase App created for you. Don’t worry about forking over any money — everything covered today falls well within Firebase’s free plan.

You’ll get routed to the Account dashboard where you’ll see that default Firebase app that was created upon sign up.

firebase-signup

Click Manage App, which takes you into the App dashboard where you can manage your Firebase app.

This app will serve as the container for all Firebase services. You’ll use it to store data and authenticate your users.

Every Firebase app has a unique URL associated with it, which you’ll use to save and sync data in Firebase. It looks something like: https://<your-firebase-app>.firebaseio.com. Navigate to it and you’ll see something like this:

firebase-app-dash

This is where you view and manage the app’s data as it updates. The main thing to note is the current URL because you’ll use to save and sync data.

Creating a Connection to Firebase

With your Firebase app set up, go to Xcode and open GroceryListTableViewController.swift. Where the properties are defined, add the following:

let ref = Firebase(url: "https://<your-firebase-app>.firebaseio.com/grocery-items")

This establishes a connection to your Firebase database using the unique URL. In the documentation, these Firebase properties are referred to as references because they refer to a location in your Firebase database.

In short, this property allows for saving and syncing of data to the given location.

You’ll notice that the base URL is not used, instead, it uses a child path of grocery-items. The Firebase database is a JSON NoSQL database, so all data is stored as JSON.

JSON is a hierarchical key-value data structure, meaning a key refers to an object that can contain keys pointing to other objects. JSON data is simply a tree of key value pairs.

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. Take a look at the following sample JSON data:

// The root of the tree
// https://<your-firebase-app>.firebaseio.com/
{
  // https://<your-firebase-app>.firebaseio.com/grocery-items
  "grocery-items": {
 
    // https://<your-firebase-app>.firebaseio.com/grocery-items/milk
    "milk": {
 
      // https://<your-firebase-app>.firebaseio.com/grocery-items/milk/name
      "name": "Milk",
 
      // https://<your-firebase-app>.firebaseio.com/grocery-items/milk/addedByUser
      "addedByUser": "David"
    },
 
    "pizza": {
      "name": "Pizza",
      "addedByUser": "Alice"
    },
  }
}

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

In the case of the data above, you can retrieve all grocery items by using the URL:

https://<your-firebase-app>.firebaseio.com/grocery-items

If you only want to get the first grocery item you can navigate to a child path using the URL:

https://<your-firebase-app>.firebaseio.com/grocery-items/milk

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

Understanding Firebase References

A fundamental theme to grasp is that a Firebase reference points a location in Firebase where data is stored. If you create multiple references, then they all share the same connection.

Have a look at this sample code:

// 1
let rootRef = Firebase(url: "https://<your-firebase-app>.firebaseio.com/")
 
// 2
let childRef = Firebase(url: "https://<your-firebase-app>.firebaseio.com/grocery-items")
 
// 3
let itemsRef = rootRef.childByAppendingPath("grocery-items")
 
// 4
let milkRef = itemsRef.childByAppendingPath("milk")
 
// 5
println(rootRef.key)   // prints: ""
println(childRef.key)  // prints: "grocery-items"
println(itemsRef.key)  // prints: "grocery-items"
println(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 childByAppendingPath(_:) to create a child reference by passing the child path. This reference is the same as the one above.
  4. Using the itemsRef, you can create a child reference to the milk location.
  5. Every reference has a key property. This property tells you what the key name is in the Firebase database.

You don’t need to add this code anywhere in the sample project, it’s purely for illustrative purposes. :]

Adding New Items to the List

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

This is where 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’s going to want to use an app that doesn’t remember or sync their grocery list! Replace saveAction with the following:

let saveAction = UIAlertAction(title: "Save",
    style: .Default) { (action: UIAlertAction!) -> Void in
 
  // 1
  let textField = alert.textFields![0] as! UITextField
 
  // 2
  let groceryItem = GroceryItem(name: textField.text, addedByUser: self.user.email, completed: false)
 
  // 3
  let groceryItemRef = self.ref.childByAppendingPath(textField.text.lowercaseString)
 
  // 4
  groceryItemRef.setValue(groceryItem.toAnyObject())
}

Here’s what’s going on:

  1. Get the text field from the alert controller.
  2. Using the current user’s data, create a new GroceryItem that is not completed by default.
  3. Create a child reference using childByAppendingPath(_:). The key value of this reference is the item’s name in lowercase, so when users add duplicate items — even if they capitalize it, 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 function to turn it into a dictionary called toAnyObject().

Build and run. Open up a browser and point it to the Firebase App dashboard and position it next to the simulator. When you add an item, you’ll see it appear in the dashboard:

fb-save

Now you have a grocery list app that adds data to Firebase in realtime! So this key feature is working correctly, but none of the items are added to the table view.

So, how about you get that data synchronizing from the database to the table view?

Retrieving Data

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

Add the following to viewDidAppear() in GroceryListTableViewController.swift:

ref.observeEventType(.Value, withBlock: { snapshot in
  println(snapshot.value)
}, withCancelBlock: { error in
  println(error.description)
})

Here you’ve added an observer that executes the given closure whenever the value that ref points to is changed.

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

This function takes two parameters, an instance of FEventType and a closure.

The event type specifies what event you want to listen for. The code above listens for a .Value event type, which in turn listens for all types of changes to the data in your Firebase database — add, removed, and changed.

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

The app is notified of the change via a closure, which is passed an instance FDataSnapshot. The snapshot, as its name suggests, represents the data at that specific moment in time. To access the data in the snapshot, you use the value property.

Synchronizing Data to the Table View

So that took care of logging — now to actually display the grocery list in your table view.

In GroceryListTableViewController.swift, replace viewDidAppear(_:) with the following:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
 
  // 1
  ref.observeEventType(.Value, withBlock: { snapshot in
 
    // 2
    var newItems = [GroceryItem]()
 
    // 3
    for item in snapshot.children {
 
      // 4
      let groceryItem = GroceryItem(snapshot: item as! FDataSnapshot)
      newItems.append(groceryItem)
    }
 
    // 5
    self.items = newItems
    self.tableView.reloadData()
  })
}

Here’s what happening:

  1. Attach a listener to receive updates whenever the grocery-items endpoint is modified.
  2. 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. The GroceryItem struct has an initializer that populates its properties using a FDataSnapshot. A snapshot’s value is of type AnyObject, and can be a dictionary, array, number, or string. After creating an instance of GroceryItem, it’s added it to the array that contains the latest version of the data.
  5. Reassign items to the latest version of the data, then reload the table view so it displays the latest version.

Build and run. Add an item — how about some pizza? — it shows up in the table view.

fb-sync

No pull-to-refresh required to get the list to update in real time!

realtime-updates

Removing Items From the Table View

The table view will synchronize on any kind of change to your data, but right now 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 users swipes it away.

Locate tableView(_:commitEditingStyle:forRowAtIndexPath:). 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 the following:

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
  if editingStyle == .Delete {
    // 1
    let groceryItem = items[indexPath.row]
    // 2
    groceryItem.ref?.removeValue()
  }
}

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

The index path’s row is used to retrieve the corresponding grocery item. Each GroceryItem has a Firebase reference property named ref, and calling removeValue() on that reference causes the listener in viewDidLoad() to 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.

fb-delete

Nice work! Your items now delete in realtime.

Checking Off Items

Now you know how to add, remove, and sync items, and that’s all pretty cool. But what about when you’re actually shopping? Should you just delete stuff that 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, so you’ll mimic that familiar behavior in this app, but with a modern twist!

When tapped, items should turn gray and show a checkmark to give the user some visual feedback that the item is no longer needed.

checked-off-grocr

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 within tableView(_:cellForRowAtIndexPath:) when the table view is first loaded, and from within tableView(_:didSelectRowAtIndexPath:) when the user taps on a row.

Replace the current implementation of tableView(_:didSelectRowAtIndexPath:) with the following:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
  // 1
  let cell = tableView.cellForRowAtIndexPath(indexPath)!
  // 2
  var 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 of what’s happening:

  1. Find the cell the user tapped using cellForRowAtIndexPath(_:).
  2. Get the corresponding GroceryItem by using the index path’s row.
  3. Negate completed on the grocery item to toggle the status.
  4. Call toggleCellCheckbox() update the visual properties of the cell.
  5. 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 on an item and see that it toggles back and forth between the complete and incomplete statuses.

fb-toggle

Congratulations, you’ve got yourself a pretty sweet grocery list app now!

Sorting the Grocery List

You know how sometimes you forget to pick up that ice cream because it’s nestled between a couple of things you’ve already marked off and your eyes played tricks on you? Well you, dear reader, can fix that.

The app would be 10x more awesome if checked items moved themselves to the bottom of the list automatically. 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 working in GroceryListTableViewController.swift, replace viewDidAppear(_:) with the following:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
 
  ref.queryOrderedByChild("completed").observeEventType(.Value, withBlock: { snapshot in
    var newItems = [GroceryItem]()
    for item in snapshot.children {
      let groceryItem = GroceryItem(snapshot: item as! FDataSnapshot)
      newItems.append(groceryItem)
    }
    self.items = newItems
    self.tableView.reloadData()
  })
}

To order the data by the completed value you call queryOrderedByChild(_:) on the Firebase reference, which takes a key to order by.

Since the list needs to order by completed, the key completed is passed to the query. Then, queryOrderedByChild(_:) returns a reference that informs the server to return data in an ordered fashion.

Build and run. Tap on a row to toggle its completion status. The completed items magically move to the bottom of the list.

fb-order

Wow! You’re really making grocery shopping easier here. Seems like it should be simple enough to sync the data across multiple users, for instance, with a significant other or housemate. This sounds like a job for…authentication!

Authenticating Users

Firebase has an authentication service that allows apps to authenticate through several providers. You can authenticate users with Google, Twitter, Facebook, Github, email & password, anonymous, and even custom backends. 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 App dashboard and click on Login & Auth.

fb-login-auth

Check Enable Email & Password Authentication.

fb-enable-login

Now you’re ready to authenticate your users using their email and password!

Registering Users

Open LoginViewController.swift. Add the following to the top of the class:

let ref = Firebase(url: "https://<your-firebase-app>.firebaseio.com")

Find signUpDidTouch(_:). This presents a UIAlertController that allows the user to register for an account. Locate saveAction and modify its closure to the following:

// 1
self.ref.createUser(emailField.text, password: passwordField.text) { (error: NSError!) in
  // 2
  if error == nil {
    // 3
    self.ref.authUser(emailField.text, password: passwordField.text,
        withCompletionBlock: { (error, auth) -> Void in
      // 4
    })
  }
}

Here’s what’s going on in the above code:

  1. Call createUser(_:password:) on the Firebase reference passing the supplied email and password.
  2. In the closure, check if there was an error.
  3. Creating a user doesn’t imply authentication. To authenticate, call authUser(_:password:withCompletionBlock:), again passing in the given email and password.
  4. For now, you simply do nothing in the closure.

Build and run. Tap the Sign up button and enter an email and a password, then tap save. The view controller won’t navigate to anything on successful login just yet. If you refresh the Firebase Login & Auth tab you’ll see the newly created user.

fb-register-user

W00T! The app now registers users and then lets them log in. Don’t celebrate yet though, you need to finish the process so people can actually use the app as intended.

Logging Users In

The Sign up button can register and log in users. However, the login button effectively does nothing because no segue is performed.

Still working in LoginViewController.swift, find loginDidTouch(_:) and modify it so it matches the following:

@IBAction func loginDidTouch(sender: AnyObject) {
  ref.authUser(textFieldLoginEmail.text, password: textFieldLoginPassword.text,
      withCompletionBlock: { (error, auth) in
 
  })
}

This code will authenticate the user when they attempt to log in by tapping the Login button.

It’s time to perform the segue to the next controller.

Observing Authentication State

Firebase has observers that allow you to monitor a user’s authentication state. This is a great place to perform a segue.

You need to replace the current viewDidAppear(_:) implementation with the following:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
 
  // 1
  ref.observeAuthEventWithBlock { (authData) -> Void in
    // 2
    if authData != nil {
      // 3
      self.performSegueWithIdentifier(self.LoginToList, sender: nil)
    }
  }
}

Here’s a run-down of what’s happening:

  1. Create an authentication observer using observeAuthEventWithBlock(_:).
  2. The block is passed the authData parameter. Upon successful user authentication, this is populated with the user’s information. If authentication fails, the variable is nil.
  3. On successfull authentication, perform the segue. Pass nil as the sender. This may seem strange, but you’ll set this up in GroceryListTableViewController.swift.

Setting the User in the Grocery List

Go to GroceryListTableViewController.swift, and add the following to the bottom of viewDidAppear(_:):

ref.observeAuthEventWithBlock { authData in
  if authData != nil {
    self.user = User(authData: authData)
  }
}

Here you attach an authentication observer to the Firebase reference, that in turn assigns the user property when a user successfully signs in.

Build and run. If a user is logged in, they bypass LoginViewController and segue to the GroceryListTableViewController. When users add items, their email will show in the detail of the cell.

fb-user-add

Success! The app now has basic user authentication.

Monitoring Users Online Status

Now that the app has user authentication, its time to detect which users are online. Open GroceryListTableViewController.swift and add the following property:

let usersRef = Firebase(url: "https://<your-firebase-app>.firebaseio.com/online")

Remember to replace your-firebase-app with your app’s name.

This is a Firebase reference that points to an online location that stores a list of online users.

Next, modify the observeAuthEventWithBlock(_:) closure inside viewDidAppear(_:):

ref.observeAuthEventWithBlock { authData in
  if authData != nil {
    self.user = User(authData: authData)
    // 1
    let currentUserRef = self.usersRef.childByAppendingPath(self.user.uid)
    // 2
    currentUserRef.setValue(self.user.email)
    // 3
    currentUserRef.onDisconnectRemoveValue()
  }
}

The code above the follows these steps:

  1. Create a child reference using a user’s uid, which is generated when Firebase creates an 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, for instance 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.

fb-monitoring

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

Updating the Online User Count

Still working in GroceryListTableViewController.swift, add the following code to viewDidAppear():

usersRef.observeEventType(.Value, withBlock: { (snapshot: FDataSnapshot!) in
  if snapshot.exists() {
    self.userCountBarButtonItem?.title = snapshot.childrenCount.description
  } else {
    self.userCountBarButtonItem?.title = "0"
  }
})

This creates an observer that is used to monitor online users. When users go on-and-offline, the title of userCountBarButtonItem updates with the current user count.

Displaying a List of Online Users

Open OnlineUsersTableViewController.swift and add the following to viewDidAppear(_:):

// 1
usersRef.observeEventType(.ChildAdded, withBlock: { (snap: FDataSnapshot!) in
  // 2
  self.currentUsers.append(snap.value as! String)
  // 3
  let row = self.currentUsers.count - 1
  // 4
  let indexPath = NSIndexPath(forRow: row, inSection: 0)
  // 5
  self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Top)
})

Here’s what’s happening in the code:

  1. Create an observer that listens for children added to the location managed by usersRef. This is different than a value listener because only the added child are passed to the closure.
  2. Take the value from the snapshot, and then append it to the local array.
  3. The current row is always the count of the local array minus one because the indexes managed by the table view are zero-based.
  4. Create an instance NSIndexPath using the calculated row index.
  5. Insert the row using an animation that causes the cell to be inserted from the top.

This will only render items as they are added rather than reloading the entire list, and it also gives you the ability to specify a nice animation. :]

Since users can go offline, the table needs to react to users being removed as well. Add the following below the code you just added:

usersRef.observeEventType(.ChildRemoved, withBlock: { (snap: FDataSnapshot!) -> Void in
  let emailToFind: String! = snap.value as! String
  for(index, email) in enumerate(self.currentUsers) {
    if email == emailToFind {
      let indexPath = NSIndexPath(forRow: index, inSection: 0)
      self.currentUsers.removeAtIndex(index)
      self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    }
  }
})

This simply adds an observer that listens for children of the usersRef reference being removed. It searches the local array for the email value to find the corresponding child item, and once located, it deletes the associated row from the table.

Build and run.

Tap Online in the Firebase users dashboard, and the current user’s email will appear in the table. Using a bit of trickery, it’s possible to add a user to Online, and once you do, it shows in the list. Click the Remove button in the App Dashboard and the user fades from existence….

fb-users-table

Booyah! The table updates when users are added and removed.

monintoring-users

Enabling Offline

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

No problem, you’ll just set up your database to work offline. Open AppDelegate.swift and add the following:

override init() {
  super.init()
  Firebase.defaultConfig().persistenceEnabled = true
}

Yup, that’s it! Just like that your app works offline. Even offline updates that occur across app restarts will apply to your Firebase database once a connection is made. Oooh-ahhhh!

Where To Go From Here?

You can download the completed version of Grocr here.

Throughout this Firebase tutorial, you’ve learned the basics of Firebase by building a collaborative grocery list app. You’ve implemented saving data to a Firebase database, syncing data from a Firebase database in realtime, authenticating users, monitoring online user status, and finally enabling offline support. And you did all this without writing a single line of server code! :]

If you’re curious about Firebase, please do check out the documentation, as well as the examples provided by Firebase themselves.

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

The post Firebase Tutorial: Getting Started appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>