Reading Firestore Data
Complete guide to reading and querying data from Cloud Firestore in iOS apps, including real-time listeners and data modeling
Note:
This tutorial covers reading, querying, and listening for real-time updates from Cloud Firestore. Reading collections from the Firestore database is a very simple process.
Overview
Reading collections from the Firestore database is a straightforward process. In this guide, we'll start with the basics of fetching data and move on to more advanced topics like real-time listeners and complex queries.
Reference:
Step 1: Basic Data Fetching
First, as with any service, start by binding Firestore to a variable within a function.
import Firebase
import FirebaseFirestore
class FeedViewController: UIViewController {
private let db = Firestore.firestore()
override func viewDidLoad() {
super.viewDidLoad()
fetchPosts()
}
func fetchPosts() {
db.collection("posts").getDocuments { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
}
}
You can listen to the IDs in the desired collection with .addSnapshotListener. If no error is encountered during this listening and the IDs are not empty, these IDs will correspond to a document. You can get the fields you want within the collection with the .get command and transfer them to an array you have added.
Here is a more complete example:
func fetchPostsWithDetails() {
db.collection("posts").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
let posts = documents.map { queryDocumentSnapshot -> Post in
let data = queryDocumentSnapshot.data()
let author = data["author"] as? String ?? ""
let text = data["text"] as? String ?? ""
return Post(author: author, text: text)
}
// Now you have an array of Post objects to use in your UI
self.displayPosts(posts)
}
}
Step 2: Real-time Listeners
For real-time updates, you can use addSnapshotListener. This will notify your app of any changes in the collection.
func listenForRealTimeUpdates() {
db.collection("posts")
.order(by: "timestamp", descending: true)
.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
print("New post: \(diff.document.data())")
// Handle new post
}
if (diff.type == .modified) {
print("Modified post: \(diff.document.data())")
// Handle modified post
}
if (diff.type == .removed) {
print("Removed post: \(diff.document.data())")
// Handle removed post
}
}
}
}
Step 3: Querying and Filtering Data
Firestore allows for powerful querying and filtering of your data.
Simple Queries
func fetchUserPosts(userId: String) {
db.collection("posts")
.whereField("authorId", isEqualTo: userId)
.getDocuments { (querySnapshot, error) in
// ... handle results
}
}
Complex Queries
You can chain multiple whereField clauses to create more complex queries. You may need to create a composite index in the Firebase Console for these queries.
func fetchPopularPosts(inCategory category: String) {
db.collection("posts")
.whereField("category", isEqualTo: category)
.whereField("likes", isGreaterThan: 100)
.order(by: "likes", descending: true)
.limit(to: 20)
.getDocuments { (querySnapshot, error) in
// ... handle results
}
}
Step 4: Data Modeling with Codable
For cleaner code, you can use Swift's Codable protocol to map Firestore documents to your custom Swift types.
Create a Codable Model
import FirebaseFirestoreSwift
struct Post: Codable, Identifiable {
@DocumentID var id: String? = UUID().uuidString
var text: String
var author: String
var authorId: String
var timestamp: Timestamp
var likes: Int = 0
}
Fetch and Decode Data
func fetchAndDecodePosts() {
db.collection("posts").getDocuments { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else if let querySnapshot = querySnapshot {
let posts = querySnapshot.documents.compactMap { document -> Post? in
try? document.data(as: Post.self)
}
// You now have an array of [Post]
self.displayPosts(posts)
}
}
}
Note:
Reading data from Firestore is a core feature for building dynamic apps. Leveraging real-time listeners and Codable can greatly simplify your codebase.