← Files FirebaseARCHIVED FILE

skills/firebase-firestore/references/standard/ios_setup.md

5.25 KB · Oct 4, 2026 · 12:18 UTC

↓ Download file

# Firebase Firestore iOS Setup Guide

# ⛔️ CRITICAL RULE: NO FirebaseFirestoreSwift ⛔️

UNDER NO CIRCUMSTANCES should you import, link against, or configure a project
to use `FirebaseFirestoreSwift`.

As of Firebase SDK v11+, all Swift-specific features (including `@DocumentID`,
`@ServerTimestamp`, and `Codable` support) have been fully merged into the main
`FirebaseFirestore` module.

- NEVER add `.external(name: "FirebaseFirestoreSwift")` or similar to SPM or
  Xcode configurations.
- NEVER write `import FirebaseFirestoreSwift` in any Swift file.
- ONLY use `import FirebaseFirestore`.

This is a zero-tolerance constraint. Using `FirebaseFirestoreSwift` is
fundamentally incorrect and unacceptable.

# ⛔️ CRITICAL RULE: NO INLINE INITIALIZATION ⛔️

NEVER write `let db = Firestore.firestore()` as an inline class or struct
property if there is ANY chance the object is instantiated before
`FirebaseApp.configure()` executes in the app root.

- **FATAL CRASH:**
  `@Observable class DataManager { let db = Firestore.firestore() }` initialized
  as a `@State` in the App root.
- **SAFE PATTERN:** Initialize `Firestore.firestore()` lazily
  (`lazy var db = Firestore.firestore()`) OR explicitly initialize the manager
  *after* `FirebaseApp.configure()` finishes.

## 1. Import and Initialize

Ensure you have installed the `FirebaseFirestore` SDK. Use the
`xcode-project-setup` skill to automate adding the SPM dependency to the Xcode
project.

```swift
import FirebaseFirestore
```

Initialize an instance of Cloud Firestore:

```swift
let db = Firestore.firestore()
```

## 2. Type-Safe Data Models (Codable)

To leverage modern Swift data modeling, define your data as `Codable` structs.
The main `FirebaseFirestore` module automatically supports mapping these types.

```swift
struct User: Codable {
    @DocumentID var id: String?
    var firstName: String
    var lastName: String
    var born: Int
}
```

## 3. Writing Data (Modern Concurrency & Codable)

Using `async/await` and `Codable` ensures type safety and avoids callback hell.

```swift
let user = User(firstName: "Ada", lastName: "Lovelace", born: 1815)

do {
    // Add a new document with a generated ID using Codable
    let ref = try db.collection("users").addDocument(from: user)
    print("Document added with ID: \(ref.documentID)")
} catch {
    print("Error adding document: \(error)")
}
```

## 4. Reading Data (Modern Concurrency & Codable)

```swift
do {
    let querySnapshot = try await db.collection("users").getDocuments()
    
    // Map documents to the User struct automatically
    let users = querySnapshot.documents.compactMap { document in
        try? document.data(as: User.self)
    }
    
    for user in users {
        print("Found user: \(user.firstName) \(user.lastName)")
    }
} catch {
    print("Error getting documents: \(error)")
}
```

## 5. Realtime Listeners in SwiftUI (Lifecycle Best Practices)

When implementing Firestore realtime listeners (`addSnapshotListener`) within a
SwiftUI application, you **MUST** tie the listener lifecycle to the view's
identity using `.task(id:)`, NOT `.onDisappear`.

### ⛔️ UNSAFE PATTERN (.onDisappear)

Presenting a `.sheet` or `.fullScreenCover` can trigger the underlying view's
`onDisappear` method. If you stop your listener here, the feed will stop
updating while the sheet is open, and won't resume when it's dismissed.

### ✅ SAFE PATTERN (.task with deinit)

Because `addSnapshotListener` is a synchronous call, placing it inside a `.task`
means the task completes immediately. This breaks SwiftUI's automatic
cancellation mechanism.

To safely manage traditional Firebase listeners in SwiftUI, you must use
**`deinit`** to handle memory cleanup when the view is destroyed, and
**`.task(id:)`** to handle data identity changes while the view is active.

```swift
import SwiftUI
import FirebaseFirestore

@MainActor
@Observable 
final class DataManager {
    private var listenerHandle: ListenerRegistration?
    var data: [String] = []
    
    func startListening(for userId: String) {
        // 1. Clean up any existing listener to prevent duplicates if the ID changes
        stopListening()
        
        // 2. Start the regular listener and capture the handle
        listenerHandle = Firestore.firestore().collection("users").document(userId).addSnapshotListener { snapshot, error in
            // Handle updates
        }
    }
    
    func stopListening() {
        listenerHandle?.remove()
        listenerHandle = nil
    }
    
    // 3. Guarantee cleanup when the View is destroyed and this object is deallocated
    isolated deinit {
        stopListening()
    }
}
```

Then, in your SwiftUI View, trigger the listener using `.task(id:)`.

```swift
struct MyView: View {
    @State private var manager = DataManager()
    @Environment(AuthManager.self) var authManager
    
    var body: some View {
        List(manager.data, id: \.self) { item in
            Text(item)
        }
        // .task(id:) automatically re-runs if the userId changes.
        // The view model handles stopping the old listener and starting the new one.
        .task(id: authManager.userId) {
            if let userId = authManager.userId {
                manager.startListening(for: userId)
            } else {
                manager.stopListening()
            }
        }
    }
}
```

SHA-256: 0e327d20a725bfe2dc151b08182540b1f4b208e0119b44b85f680085d628a071