skip to Main Content

Learning Objectives

  1. Understand Cloud Firestore Document and Query Snapshots.
  2. Learn how to set up a Listener to detect and load any changes to a collection.
  3. Implement a QuerySnapshot Listener to return data to all logged in apps if any documents in a collection have been deleted or changed.
  4. Write a convenience initializer that accepts a dictionary to create an instance of a class.
  5. Use an escaping closure to reload a table view’s data after a listener returns data.

Video Lesson:

Reference

Getting Data from Cloud Firestore: Snapshots & Snapshot Listeners

Cloud Firestore retrieves data in a structure it calls a Snapshot. A DocumentSnapshot contains data for a single document, while a QuerySnapshot returns data for zero or more documents.  Our app returns a QuerySnapshot with all documents in a collection (e.g. the documents in the “spots” collection in this video lesson), however it’s also possible to return a query with specific criteria (say with averageReviews >= 4.0). While we haven’t worked with complex queries, you can find more information in the Cloud Firestore documentation.

One of the best features of Cloud Firestore is real-time updates, that is the ability to push any data changes out to all apps. To “listen” for changes on a document or collection, we create a snapshot listener.  Our app created a listener that listened to the “spots” collection, and returned a querySnapshot of all documents in “spots” whenever changes were made. This means that whenever a record is added, updated, or deleted, the listener will grab updated data and push it out to any apps that are logged into Cloud Firestore.

We wrote the listener in our Spots class’s .loadData method. The method was written with an @escaping closure, that allows us to put a self.tableView.reloadData() call in curly braces after a call to Spots.loadData so that any snapshot update will automatically refresh data in a table view.  No need to use the “pull to refresh” option that you may have seen in other apps.

A listener also eliminates the need to have to pass saved or edited data back to a source table view controller. For example, when we save data in SpotDetailViewController.swift, this is picked up by a “spots” collection listener that was set up when we first loaded SpotsListViewController (through a call to spots.loadData. When we return to SpotsListViewController.swift, after saving a Spot record (using .saveData), the listener pushes data changes to the app, and the tableView.reloadData() in the curlies that we placed after spots.loadData, will reload the latest data in the table. We don’t even need to use an unwind method or even a formal segue from the SpotDetailViewController to SpotsListViewController. The same code used in our “Cancel” button can be called in our “Save” button if we’ve confirmed a successful call to spot.saveData.

Implementing our “spots” collection listener

Before adding a .loadData method to our Spots class, we first import Firebase, we declare property to hold an of Firestore (named db, below), and we create an initializer that also initializes the Firestore property db. Creating db in our initializer will allow any instance of Spots (our spots variable in SpotsListViewController.swift) to have an active connection to our database, so that it can provide regular updates.

Add an import Firebase line, declare a Firestore! class property (db), and create an initializer that initializes (db) to Firestore.firestore()

The code below shows the .loadData method we wrote in our Spots class. This method creates a listener for any changes made to the “spots” collection, clears out any old data from spotArray property, loads data from each document in the querySnapshot documents returned, and appends this data into spotArray.

Data returned from Cloud Firestore also arrives as a [String: Any] dictionary. Any valid document returned from Cloud Firestore has a .data() method, that will return a dictionary of the data in that document. We’ve written another convenience initializer in Spot that accepts a [String: Any]dictionary, and creates a new Spot from this data (the reverse of our dictionary calculated property, which took data from its Spot and created a dictionary.

The diagram below shows the Spots.loadData method, with callouts describing the key steps.

The Spots.loadData method

A Convenience Initializer that Creates an instance from a dictionary

The code below is a convenience initializer for the Spot class. By calling Spot(dictionary:) with the [String: Any] dictionary returned in a Firebase Document (accessed via a document’s .data() method), we can create a new instance of Spot.

The Spot class’s init(dictionary: [String:Any] convenience initializer

Calling .loadData to set up our collection listener

Using our MVC approach, all we need to do in SpotsListViewController.swift is make the call below in viewDidAppear. If at some point in the future we change the the database that we use, we’d only need to modify our Spots/Spot classes, and not touch the code in our SpotsListViewController.swift or SpotDetailViewController.swift files.

SpotListViewController.swift’s viewWillAppear, featuring spots.loadData

Full Stack!

Full Stack: User Interface, Application Logic, and Database Access

The term full-stack developer refers to a programmer that can implement all layers of a computer application – user interface, application logic, and shared database access. A full stack developer isn’t necessarily an expert in all layers, but they can build a working app that leverages all components.  Cloud Firestore allowed us to quickly build a “full-stack” product. Congratulations on beginning your full-stack journey!

Key Takeaway

  1. Cloud Firestore returns updates as a DocumentSnapshot, or a multi-document QuerySnapshot.
  2. QuerySnapshot can contain zero or more documents in its .documents property. A given document’s data is returned in a [String: Any] dictionary using the document’s .data() method.
  3. convenience initializer that accepts a dictionary and returns an instance of a class can be an easy way to create new objects from data returned via Cloud Firestore.
  4. snapshot listener will pay attention to any database updates on a document or collection, and can return results if it detects any changes. Results are automatically pushed out to all signed-in client apps. No need to “pull to refresh” or make any additional calls.
  5. A snapshot listener can eliminate the need to pass data from a detail view to a table view. Since a listener is always “listening” for updates, returning to the screen with a table view will automatically load any changes, without requiring the source to prepare(for segue:), or the destination to unwind the segue.
  6. The term full-stack developer refers to a programmer that can implement all layers of a computer application – user interface, application logic, and shared database access.  Cloud Firestore allowed us to quickly build a “full-stack” product.
Back To Top