Skip to content
This repository has been archived by the owner on Sep 23, 2024. It is now read-only.

Get Started with EasyFirestore

Ben Myers edited this page Apr 19, 2022 · 6 revisions

Overview

Welcome to EasyFirestore! πŸŽ‰

EasyFirestore makes it fast and easy to:

  • store, update, and retrieve documents quickly
  • format data into documents and models that are ready to use with Firestore
  • easily link child documents to a list of document ID's in a parent document
  • retrieve a list of child documents given a list of ID's
  • query for documents in a Swift-y manner

Benefits of EasyFirestore:

  • πŸ“¦ Out-of-the-box cacheing saves your Firestore read count
  • πŸ€” Tackle database problems with ease using EasyFirestore's linking methods
  • πŸ“ˆ Automatically track metrics such as document indexes using the Singleton protocol

Requisites

What You'll Learn

In this page, you'll learn about:

  1. Setting up Firestore in Firebase Console β†’
  2. Create a Document Type β†’
  3. Sending a Document to Firestore β†’
  4. Linking to an Array of Document IDs β†’
  5. Retrieving Data from Firestore β†’
  6. Listening to Data Updates from Firestore β†’
  7. Querying for Documents in Firestore β†’
  8. The Document Cache β†’
  9. Managing One-Document Storage with Singletons β†’

Get Started

Setting up Firestore in Firebase Console

To get started, you'll need to set up Firestore in your Firebase console. Find the Firestore tab and click the Get Started button.

Start in test mode, then click Next.

Pick a suitable database location, then click Enable.

You're good to go!

Create a Document Type

You'll first need to create a type that conforms to the Document protocol:

class ExampleDocument: Document {
  
}

Every class that conforms to Document has two required properties:

Property Name Description
id The unique identifier of the document.
dateCreated The date the document object was created.

You'll need to add your own custom properties as well:

class ExampleDocument: Document {
  
  // These properties are required by the Document protocol.
  var id: String
  var dateCreated: Date
  
  // These properties are custom.
  var foodName: String = ""
  var calories: Int = 0
}

Sending a Document to Firestore

Once you have local instances of your Document, you can easily set your documents in Firestore using:

myDocument.set()

Equivalently, you can use the EasyFirestore.Storage helper struct to send the document:

EasyFirestore.Storage.set(myDocument)

Most EasyFirestore methods like set() have optional completion handlers, which are called when the operation is complete:

myDocument.set(completion: { error in
  if let error = error {
    print("An error occured! \(error.localizedDescription)")
  } else {
    // No problems setting the document in Firestore.
  }
})

Conversely, the document can easily be deleted:

EasyFirestore.Removal.remove(myDocument)

Linking to an Array of Document IDs

Every document has an id. If you wish to store a list of documents in another document type, it's useful to store a list of document IDs rather than the documents themselves to save on the size of a document. EasyFirestore makes document linking easy with pre-defined methods.

To see this in action, consider a Dealership document type with a list of car ID's, cars, of document type Car:

class Dealership: Document {
  // DocumentID is equivalent to a String, and is available when you import EasyFirebase.
  var cars: [DocumentID]

  // ...
} 

class Car: Document {
  var make: String
  var model: String
  var year: Int

  // ...
}

We create a Car object and link it to the dealership:

let car = new Car(make: "Toyota", model: "Corolla", year: 2017)
car.assign(toField: "cars", using: \.cars, in: dealership)

If you wish to link the object and send it, you can use setAssign(...):

car.setAssign(toField: "cars", using: \.cars, in: dealership)

Again, the EasyFirestore.Linking helper struct can be used to do this.

ℹ️ Quick Tip EasyFirestore has many helper structs, including Storage, Retrieval, Querying, Updating, Removal, Linking, Cacheing, and Listening.

Retrieving Data from Firestore

You can use the EasyFirestore.Retrieval helper struct to fetch a document of a given type:

EasyFirestore.Retrieval.get(id: someDocumentID, ofType: ExampleDocument.self) { document in
  // Check that the document exists...
  guard let document = document else { return }
  // Run code related to the document here.
}

If you have a list of document IDs, you can fetch an array of documents of the same type:

EasyFirestore.Retrieval.get(ids: [docID1, docID2, docID3], ofType: ExampleDocument.self) { documents in
  // If no documents are successfully retrieved, an empty array will be passed.
  allDocs.append(documents)
}

Returning to our car/dealership example, we can also retrieve an array of child documents based on the parent document's ID array field:

EasyFirestore.Retrieval.getChildren(from: \.cars, in: dealership, ofType: Dealership.self, onFetch: { documents in
  // Passes all of the car objects with IDs given by dealership.cars
})

Listening to Data Updates from Firestore

Just as you can retrieve a document from Firestore once, you can also update a document locally whenever the document updates in Firestore.

EasyFirestore.Listening.listen(to: myDocumentID, ofType: ExampleDocument.self, key: "listenerKeyName", onUpdate: { document in
  // Check the document exists...
  guard let document = document else { return }
  // The updated document is safely available here.
})

Querying for Documents in Firestore

You can match an array of documents of a given type:

EasyFirestore.Querying.where(\ExampleDocument.name, .equals, "John Appleseed", completion: { documents in
  // The queried documents are passed here.
})

There are various ways to match queries, called Comparisons:

Comparison Description
.equals The field value in Firestore equals the provided parameter
.notEquals The field value in Firestore does not equal the provided parameter
.lessThan The field value in Firestore is less than the provided parameter
.lessEqualTo The field value in Firestore is less than or equal to the provided parameter
.greaterThan The field value in Firestore is greater than the provided parameter
.greaterEqualTo The field value in Firestore is greater than or equal to the provided parameter
.contains The field value array in Firestore contains the provided parameter
.containsAnyOf The field value array in Firestore contains any element of the provided parameter array
.in The field value in Firestore is an element of the provided parameter array
.notIn The field value in Firestore is not an element of the provided parameter array

You can order and limit data, as well:

EasyFirestore.Querying.where(\.ExampleDocument.name, .in, ["Albert", "Ben", "Carey"], order: .ascending, limit: 8, completion: {
  // A maximum of 8 documents, sorted in ascending order by the name field.
})

When using the order parameter (.ascending or .descending), the documents are sorted by the provided field.

Additionally, you can chain multiple queries:

EasyFirestore.Querying.where((\ExampleDocument.age, .greaterThan, 18), (\.name, .lessThan, "L"), completion: { document in
  // ...
})

When chaining, be sure to follow the Firestore Querying Limitations.

The Document Cache

Sometimes you'll repeatedly access documents, which can spike your read count. To avoid this, EasyFirestore automatically caches documents for future retrieval.

Documents that are retrieved from Firestore are automatically cached. By default, documents fetched using EasyFirestore.Retrieval.get(...) rely on the cache to get previously fetched documents. To bypass this, and to always receive up-to-date documents, pass false to the useCache parameter:

EasyFirestore.Retrieval.get(id: myDocumentId, ofType: ExampleDocument.self, useCache: false, completion: { document in
  // ...
})

You can interact with the cache manual by using the EasyFirestore.Cacheing helper struct.

// Manually store the document in the cache
EasyFirestore.Cacheing.register(myDocument)
// Manually retrieve the document from the cache
EasyFirestore.Cacheing.grab(myDocumentID, fromType: ExampleDocument.self)

It is possible to bypass cache usage entirely:

EasyFirebase.useCache = false

Managing One-Document Storage with Singletons

If you want to store data that is global to all users/devices, use a Singleton. Singletons are of type Document, but they have a few special properties. First, their id is simply a name used to define the singleton.

class ExampleSingleton: Singleton {
  
  // Required by the Singleton protocol
  var id: SingletonName
  var dateCreated: Date
  
  // Custom, one-time properties
  var totalUsers: Int = 0
  var totalCars: Int = 0
}

To store the singleton properly, you must use the EasyFirestore.Storage.set(_:) method:

EasyFirestore.Storage.set(mySingleton)

Such a singleton will be stored in a collection called "Singleton" with a name based on the singleton's id value.

Next Steps

πŸ”‘ Get Started with EasyAuth