Firebase & SwiftUI

Firestore’s @FirestoreQuery property wrapper for SwiftUI

Brianna Doubt
Firebase Developers
4 min readDec 28, 2021

--

Firebase has long been a place for developers to rapidly prototype a functional front-end that interfaces with a full-fledged enterprise level cloud system.

For us Swift developers, Firebase has supported us as the language has grown. Firebase was made available via Swift Package Manager recently, and there’s a lot of new APIs that came out for SwiftUI with that particular release. Today, I want to walk us through a few, but really one in particular: @FirestoreQuery(collection:) !!!

Prerequisites

I’m hoping this article is easily digestible if you know what you’re doing with Firebase. But if you’re on the steeper part of the learning curve — worry not.

Let me outline the bare minimum required for getting a FirestoreQuery up and running.

  1. A Firebase project (set up or login to an existing Firebase project in your Firebase Console).
  2. An Xcode project with an iOS, macOS, or tvOS app target. (FirestoreQuery — or Firestore for that matter — does not yet work on watchOS)
  3. The Firebase Apple SDK downloaded and installed into your project via Cocoapods or Swift Package Manager.
  4. Link the library FirebaseFirestoreSwift to your target’s linked libraries and frameworks.

See the Firebase documentation for more details on installation.
👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

The API

FirebaseFirestoreSwift has some new APIs that make querying for your documents a breeze.

@FirestoreQuery

The new @FirestoreQuery(collection:) property wrapper is a pretty darn flexible little API. Here is a very simple example of how you can listen for a collection of documents, and display them in a List view using SwiftUI:

Minimal example for using a `@FirestoreQuery`

If you run this code in your app you’ll… probably see nothing… if there’s nothing in your database, that is. Hopefully though you have an existing data model and BLAM, you’ve displayed like all of them in your list.

If you in fact don’t have any data in your database, leave your new app running in your simulator (or on device) and navigate to your Firestore dashboard in your Firebase project. Create a collection called foos and add a new object with an autogenerated id, and a string attribute called bar.

Like magic, there should now be a new view displayed in your list!

@DocumentID

You might’ve noticed a property wrapper in the model code above:

DocumentID is a really easy way to tell Firebase that, when this object is encoded to Firebase, the system knows how to map this struct to a Firestore document. Peter Friese wrote a really wonderful article outlining this property wrapper, check it out here!

@ServerTimestamp

Another property wrapper that Firebase provides us is:

If this is left nil then Firebase will auto-generate a UTC timestamp based on the server’s clock. This variable also handles race conditions across multiple clients, which is an incredibly complex set of problems to handle.

For example: Imagine a chat app that is leveraging a Chat model object. Chat includes an array of Message objects called messages. As each user sends their message, we append the last message to Chat.messages and save that new Message document with a new updated timestamp variable.

Normally, this would result in some unreliable race conditions. But, wrapping the updated variable within a @ServerTimestamp property wrapper will leverage Firebase to timestamp the documents in the order in which they were saved on the client side. That way, the chat is always in the order which the messages were sent, even when there is not a network connection. Pretty fancy!

Google, y’all write some cool code.

Predicates

Often times when we design apps with Firestore us savvy developers will rely on querying our data to show the user the data that they’re interested in. We need to filter our documents to get just the right amount of information to give our users the best UX possible. We also need to keep our number of document reads to a minimum (cause that sh*t’s expensive at scale!).

As the name implies, @FirestoreQuery offers an easy way to query your collections using the @FirestoreQuery(collection:predicates:) method:

An example of a `@FirestoreQuery` running with a predicate to filter out data.

Often times we need to dynamically add filters depending on the user’s search criteria. Well, the Firebase team has made this one pretty slick:

There are a bunch of different predicates that are viable to use:

  • isEqualTo(_ field: String, _ value: Any)
  • isIn(_ field: String, _ values: [Any])
  • isNotIn(_ field: String, _ values: [Any])
  • arrayContains(_ field: String, _ value: Any)
  • arrayContainsAny(_ field: String, _ values: [Any])
  • isLessThan(_ field: String, _ value: Any)
  • isGreaterThan(_ field: String, _ value: Any)
  • isLessThanOrEqualTo(_ field: String, _ value: Any)
  • isGreaterThanOrEqualTo(_ field: String, _ value: Any)
  • orderBy(_ field: String, _ value: Bool)
  • limitTo(_ value: Int)
  • limitToLast(_ value: Int)

Visit Google’s official documentation for more information on the intricacies of querying for your data:

Error Handling

The same way that we can access the predicates dynamically, we can also access an optional error to display or log errors as needed:

An example view displaying an error returned from a `@FirestoreQuery`

Alternatively, you specify the FirestoreQuery as a Result<[Foo], Error> value and handle the switch case in the body to conditionally show an error:

Conclusion

@FirestoreQuery is an incredibly powerful little API that will make fetching data from Firestore a lot easier. For real, it’s helped me do a ton in a very short amount of time.

If you have any questions or adjustments on how this works leave me a comment here.

I hope to be writing more, so please subscribe to my blog and I’ll see you in the next one!

--

--