Skills로 돌아가기
firebase/agent-skills검사 통과

SKILL DETAIL

firebase-firestore

firebase/agent-skills/firebase-firestore

This skill is used to set up, manage, query, and configure Cloud Firestore databases (Standard/Enterprise edition). It covers data modeling, security rules, indexes, and SDK integrations for Web, Python, iOS, Android, and Flutter. Use it when creating or listing Firestore databases, defining data models or indexes, writing SDK queries, or integrating Firestore SDKs. The skill works best with the Firebase CLI but does not require it. First, identify the Firestore instance edition (Standard or Enterprise) by running `npx -y firebase-tools@latest firestore:databases:list`. Then, follow the appropriate guides based on the edition. If no database exists, default to creating an Enterprise edition database.

설치 수 · 592출처 보기

Installation

npx skills add https://github.com/firebase/agent-skills --skill firebase-firestore

스킬 파일

SKILL.md

최근 동기화 · 2026. 10. 23.

references/enterprise/android_sdk_usage.md›
# Cloud Firestore (Enterprise edition) - Android Setup Guide (Kotlin)

This guide describes the SDK setup and basic usage patterns for Cloud Firestore
(Enterprise edition in Native mode) in an Android app using Kotlin DSL
(`build.gradle.kts`) and Kotlin code.

## Prerequisites

IMPORTANT: Before specifically working with Cloud Firestore, make sure to use
the skill and reference `firebase_basics/references/android_setup` to ensure the
following is done.

- The Firebase CLI is available and authenticated.
- An Android project exists and is registered with a Firebase Project.
- The Android project has a Firebase config file (`google-services.json`) and
  the Google services Gradle plugin (`google-services`).

______________________________________________________________________

## 1. Provision Firestore

Follow the instructions in
`firebase_firestore/references/enterprise/provisioning` to do the following:

- Provision a Firestore instance (Enterprise edition in Native mode)
- Create or append a `firebase.json` file
- Create a `firestore.rules` file
- Create a `firestore.indexes.json` file
- Deploy database, rules, and indexes
- Run Firestore locally for development and testing

______________________________________________________________________

## 2. Add dependencies to Gradle build files

> [!IMPORTANT] **[AGENT] RESOLVING THE SDK VERSION DETERMINISTICALLY** Never
> guess or hardcode a memorized out-of-date SDK or plugin version. Before adding
> dependencies, you MUST query the latest available versions directly from the
> Google Maven Repository:
>
> ```bash
> # Find the latest Firebase Android BoM version
> curl -s https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-bom/maven-metadata.xml | grep -oE '<latest>[^<]+' | cut -d'>' -f2
>
> # Find the latest firebase-firestore version
> curl -s https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-firestore/maven-metadata.xml | grep -oE '<latest>[^<]+' | cut -d'>' -f2
> ```
>
> If the dependency is already declared, fetch the exact resolved version
> currently active in the workspace's build graph:
>
> ```bash
> ./gradlew -q :app:dependencyInsight --dependency firebase-firestore --configuration releaseRuntimeClasspath
> ```

In the **module (app-level)** `build.gradle.kts` (usually
`<project>/<app-module>/build.gradle.kts`), add the dependency for Cloud
Firestore:

```kotlin
dependencies {
    // [AGENT] Fetch the latest resolved version using the Gradle command above
    implementation(platform("com.google.firebase:firebase-bom:<latest_bom_version>"))

    // Add the dependency for the Cloud Firestore library
    // When using the BoM, don't specify versions in Firebase library dependencies
    implementation("com.google.firebase:firebase-firestore")
}
```

______________________________________________________________________

## 3. Initialize Firestore

Initialize the `FirebaseFirestore` instance in your `ComponentActivity` (Jetpack
Compose) or standard Activity/Fragment:

```kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import com.google.firebase.Firebase
import com.google.firebase.firestore.FirebaseFirestore

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Initialize the Firestore instance
        val db = FirebaseFirestore.getInstance("<database-name>")

        setContent {
            MaterialTheme {
                Text("Firestore initialized!")
            }
        }
    }
}
```

______________________________________________________________________

## 4. Decision framework: Mandatory pipeline architecture

> [!CRITICAL] **MANDATORY PIPELINE ARCHITECTURE (ZERO TOLERANCE)** You are
> strictly forbidden from using standard collection queries (e.g.,
> `db.collection(...).where(...)` or client-side mapping loops) as the primary
> implementation for relational joins, aggregations, or search algorithms.
>
> Server-side execution via pipelines is the superior, non-negotiable core
> architecture for Enterprise instances.

### Strict rules & accountability

1. **Mandatory Execution Engine**: Every repository lookup requiring related
   data mapping, textual relevance scoring, or multi-document counting MUST be
   authored natively using the server-side `db.pipeline()` API.
1. **Required Proof of Use**: In your task execution summaries, you MUST
   explicitly highlight the exact pipeline stages (`.define()`, `.addFields()`,
   `.search()`, `.aggregate()`) integrated into your primary data layer return
   paths. Failure to implement pipelines natively constitutes an immediate
   violation of instructions.
1. **Check API References**:
   - **Locate**: Locate the standard Maven sources JAR (`-sources.jar`)
     corresponding to the resolved `firebase-firestore` dependency version
     inside your local Gradle or Maven dependency cache.
   - **Extract Once**: Extract the documentation files `pipeline.docs.txt` and
     `expressions.docs.txt` from the root directory of that `-sources.jar`
     archive into a temporary workspace scratch directory of your choice.
   - **Read & Reference**:
     - **Read** the extracted `pipeline.docs.txt` once fully to understand core
       pipeline structure and stage capabilities.
     - **Reference** the extracted `expressions.docs.txt` on-demand for specific
       function overloads and parameters.

______________________________________________________________________

## 5. Pipeline examples

### Relational joins pattern

When querying related data (e.g., articles and their author profiles), perform
the join at the database level via pipeline stages instead of executing multiple
sequential lookups on the client-side.

- Use `.define()` to bind parameters or document properties as variables.
- Use `.addFields()` and a nested subquery with a matching filter.
- Use `.toScalarExpression()` to convert a nested pipeline subquery to a single
  field value.
- Assign variable and field aliases using `.alias(...)` (note: while the Web SDK
  uses `.as()`, the Kotlin SDK uses `.alias()` to avoid keyword conflicts with
  Kotlin's `as` operator).

```kotlin
import com.google.firebase.firestore.pipeline.Expression.field
import com.google.firebase.firestore.pipeline.Expression.variable

// Fetch articles and join the associated author Profile side-by-side
val articlesWithAuthProfile = db.pipeline().collection("articles")
    .define(field("authorUid").alias("author_id"))
    .addFields(
        db.pipeline().collection("users")
            .where(field("__name__").documentId().equal(variable("author_id")))
            .select(field("displayName"), field("avatarUrl"), field("handle"))
            .toScalarExpression()
            .alias("author")
    )
```

### Full-text search

Leverage the database-native `.search()` stage within your pipelines to run
high-performance text query matches on the database level.

```kotlin
import com.google.firebase.firestore.pipeline.Expression.documentMatches
import com.google.firebase.firestore.pipeline.Expression.score
import com.google.firebase.firestore.pipeline.SearchStage

// Execute full-text search inside a pipeline, sorted by relevance score descending
val searchPipeline = db.pipeline()
    .collection("articles")
    .search(
        SearchStage.withQuery(documentMatches("machine learning"))
            .withSort(score().descending())
    )
    .limit(5)
```

______________________________________________________________________

## 6. Real-time listener & document operations

When real-time data sync or transaction-based document mutations are strictly
required by application specifications, write clean operations as shown in this
comprehensive example.

```kotlin
import android.util.Log
import com.google.firebase.Firebase
import com.google.firebase.firestore.DocumentChange
import com.google.firebase.firestore.firestore

val db = Firebase.firestore("<database-name>")
// 1. Add a new document to a collection
val taskData = hashMapOf(
    "title" to "Refactor Android SDK Usage Guide",
    "status" to "pending"
)

db.collection("tasks")
    .add(taskData)
    .addOnSuccessListener { documentReference ->
        val taskId = documentReference.id
        Log.d("Firestore", "Document added with ID: $taskId")

        // 2. Update specific fields of an existing document without replacing it
        db.collection("tasks").document(taskId)
            .update("priority", "high")
            .addOnSuccessListener {
                Log.d("Firestore", "Document successfully updated!")
            }
            .addOnFailureListener { e ->
                Log.w("Firestore", "Error updating document", e)
            }
    }
    .addOnFailureListener { e ->
        Log.w("Firestore", "Error adding document", e)
    }

// 3. Establish a real-time listener on a collection query
db.collection("tasks")
    .whereEqualTo("status", "pending")
    .addSnapshotListener { snapshot, error ->
        if (error != null) {
            Log.w("Firestore", "Listen failed.", error)
            return@addSnapshotListener
        }

        snapshot?.documentChanges?.forEach { change ->
            val docId = change.document.id
            val docData = change.document.data
            when (change.type) {
                DocumentChange.Type.ADDED -> {
                    Log.d("Firestore", "Added Task: $docId => $docData")
                }
                DocumentChange.Type.MODIFIED -> {
                    Log.d("Firestore", "Updated Task: $docId => $docData")
                }
                DocumentChange.Type.REMOVED -> {
                    Log.d("Firestore", "Removed Task: $docId => $docData")
                }
            }
        }
    }
```
references/enterprise/data_model.md›
# Firestore Data Model Reference

Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there
are no tables or rows. Instead, you store data in **documents**, which are
organized into **collections**.

## Document Data Model

Data in Firestore is organized into documents, collections, and subcollections.

### Documents

A **document** is a lightweight record that contains fields, which map to
values. Each document is identified by a name. A document can contain complex
nested objects in addition to basic data types like strings, numbers, and
booleans. Documents are limited to a maximum size of 1 MiB.

Example document (e.g., in a `users` collection):

```json
{ "first": "Ada", "last": "Lovelace", "born": 1815 }
```

### Collections

Documents live in **collections**, which are containers for your documents. For
example, you could have a `users` collection to contain your various users, each
represented by a document. * Collections can only contain documents. They cannot
directly contain raw fields with values, and they cannot contain other
collections. * Documents within a collection can contain different fields. * You
don't need to "create" or "delete" collections explicitly. After you create the
first document in a collection, the collection exists. If you delete all of the
documents in a collection, the collection no longer exists.

### Subcollections

Documents can contain subcollections natively. A subcollection is a collection
associated with a specific document. For example, a user document in the `users`
collection could have a `messages` subcollection containing message documents
exclusively for that user. This creates a powerful hierarchical data structure.

Data path example: `users/user1/messages/message1`

## Collection Group Support

A **collection group** consists of all collections with the same ID. By default,
queries retrieve results from a single collection in your database. Use a
collection group query to retrieve documents from a collection group instead of
from a single collection.

### Use Cases

Collection group queries are useful when you want to query across multiple
subcollections that share the same organizational structure.

For example, imagine an app with a `landmarks` collection where each landmark
has a `reviews` subcollection. If you want to find all 5-star reviews across
*all* landmarks, it would involve checking many separate `reviews`
subcollections. With a collection group, you can perform a single query against
the `reviews` collection group.

### Examples

**Standard Query** (Single Collection): Find all 5-star reviews for a specific
landmark.

```javascript
db.collection('landmarks/golden_gate_bridge/reviews').where('rating', '==', 5)
```

**Collection Group Query**: Find all 5-star reviews across *all* landmarks.

```javascript
db.collectionGroup('reviews').where('rating', '==', 5)
```
references/enterprise/flutter_setup.md›
# Cloud Firestore in Flutter

This guide covers basic CRUD operations, type-safe data modeling, and real-time
streams when using Cloud Firestore in a Flutter application via the
`cloud_firestore` package.

## 1. Setup

Ensure you have added the required dependency:

```bash
flutter pub add cloud_firestore
```

Also, ensure FlutterFire is configured properly for your target platforms.

______________________________________________________________________

## 2. Best Practices: Type-Safe Models

Instead of passing raw `Map<String, dynamic>` maps throughout your UI layer,
define a domain model class with `fromFirestore` and `toFirestore` converters to
maintain type safety.

```dart
import 'package:cloud_firestore/cloud_firestore.dart';

class Item {
  final String id;
  final String name;
  final String ownerId;
  final DateTime createdAt;

  Item({
    required this.id,
    required this.name,
    required this.ownerId,
    required this.createdAt,
  });

  factory Item.fromFirestore(DocumentSnapshot doc) {
    final data = doc.data() as Map<String, dynamic>? ?? {};
    return Item(
      id: doc.id,
      name: data['name'] as String? ?? '',
      ownerId: data['ownerId'] as String? ?? '',
      createdAt: data['createdAt'] is Timestamp 
          ? (data['createdAt'] as Timestamp).toDate() 
          : DateTime.now(),
    );
  }

  Map<String, dynamic> toFirestore() {
    return {
      'name': name,
      'ownerId': ownerId,
      'createdAt': Timestamp.fromDate(createdAt),
    };
  }
}
```

______________________________________________________________________

## 3. The Service Layer

Encapsulate all database interactions within a dedicated service class to keep
your UI code clean and testable.

### Initialization & References

```dart
class ItemService {
  // For Enterprise Native Mode, you often need to specify a non-default database ID:
  final FirebaseFirestore _db = FirebaseFirestore.instanceFor(
    app: Firebase.app(),
    databaseId: 'my-database-id',
  );

  // Define your collection reference
  CollectionReference get _itemsRef => _db.collection('items');

  // 1. Create Data
  Future<void> createItem(Item item) async {
    try {
      await _itemsRef.add(item.toFirestore());
    } catch (e) {
      print("Error creating document: $e");
    }
  }

  // 2. Read Data (One-Time Fetch)
  Future<List<Item>> fetchItems(String ownerId) async {
    try {
      final querySnapshot = await _itemsRef
          .where('ownerId', isEqualTo: ownerId)
          .orderBy('createdAt', descending: true)
          .get();

      return querySnapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
    } catch (e) {
      print("Error fetching documents: $e");
      return [];
    }
  }

  // 3. Read Data (Real-Time Stream)
  Stream<List<Item>> streamItems(String ownerId) {
    return _itemsRef
        .where('ownerId', isEqualTo: ownerId)
        .snapshots()
        .map((snapshot) {
          // If a custom composite index is missing during prototyping, apply sorting client-side:
          final items = snapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
          items.sort((a, b) => b.createdAt.compareTo(a.createdAt));
          return items;
        });
  }

  // 4. Update Data
  Future<void> updateItemName(String id, String newName) async {
    try {
      await _itemsRef.doc(id).update({'name': newName});
    } catch (e) {
      print("Error updating document: $e");
    }
  }

  // 5. Delete Data
  Future<void> deleteItem(String id) async {
    try {
      await _itemsRef.doc(id).delete();
    } catch (e) {
      print("Error deleting document: $e");
    }
  }
}
```

______________________________________________________________________

## 4. Listening to Streams in the UI (`StreamBuilder`)

Use Flutter's `StreamBuilder` to rebuild the interface reactively whenever data
changes in your database collection.

```dart
StreamBuilder<List<Item>>(
  stream: itemService.streamItems(currentUser.uid),
  builder: (context, snapshot) {
    if (snapshot.hasError) {
      return const Center(child: Text('Failed to load data'));
    }

    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(child: CircularProgressIndicator());
    }

    final items = snapshot.data ?? [];

    if (items.isEmpty) {
      return const Center(child: Text('No items found.'));
    }

    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return ListTile(
          title: Text(item.name),
          trailing: IconButton(
            icon: const Icon(Icons.delete),
            onPressed: () => itemService.deleteItem(item.id),
          ),
        );
      },
    );
  },
);
```
references/enterprise/indexes.md›
# Firestore Indexes Reference

Indexes helps to improve query performance. Firestore Enterprise edition does
not create any indexes by default. By default, Firestore Enterprise performs a
full collection scan to find documents that match a query, which can be slow and
expensive for large collections. To avoid this, you can create indexes to
optimize your queries.

## Index Structure

An index consists of the following:

- a collection ID.
- a list of fields in the given collection.
- an order, either ascending or descending, for each field.

### Index Ordering

The order and sort direction of each field uniquely defines the index. For
example, the following indexes are two distinct indexes and not interchangeable:

- Field name `name` (ascending) and `population` (descending)
- Field name `name` (descending) and `population` (ascending)

### Index Density

Dense indexes: By default, Firestore indexes store data from all documents in a
collection. An index entry will be added for a document regardless of whether
the document contains any of the fields specified in the index. Non-existent
fields are treated as having a NULL value when generating index entries.

Sparse indexes: To change this behavior, you can define the index as a sparse
index. A sparse index indexes only the documents in the collection that contain
a value (including null) for at least one of the indexed fields. A sparse index
reduces storage costs and can improve performance.

### Unique Indexes

You can use unique index option to enforce unique values for the indexed fields.
For indexes on multiple fields, each combination of values must be unique across
the index. The database rejects any update and insert operations that attempt to
create index entries with duplicate values.

## Query Support Examples

- **Simple Equality**: `where("a", "==", 1)`
  - Index Required: Single-Field Index on field `a`
- **Simple Range/Sort**: `where("a", ">", 1).orderBy("a")`
  - Index Required: Single-Field Index on field `a`
- **Multiple Equality**: `where("a", "==", 1).where("b", "==", 2)`
  - Index Required: Single-Field Index on field `a` and `b`
- **Equality + Range/Sort**: `where("a", "==", 1).where("b", ">", 2)`
  - Index Required: **Composite Index** on field `a` and `b`
- **Multiple Ranges**: `where("a", ">", 1).where("b", ">", 2)`
  - Index Required: **Composite Index** on field `a` and `b`
- **Array Contains + Equality**:
  `where("tags", "array-contains", "news").where("active", "==", true)`
  - Index Required: **Composite Index** on field `tags` and `active`

If no indexes is present, Firestore Enterprise will perform a full collection
scan to find documents that match a query.

## Management

### Config files

Your indexes should be defined in `firestore.indexes.json` (pointed to by
`firebase.json`).

Define a dense index:

```json
{
  "indexes": [
    {
      "collectionGroup": "cities",
      "queryScope": "COLLECTION",
      "density": "DENSE",
      "fields": [
        { "fieldPath": "country", "order": "ASCENDING" },
        { "fieldPath": "population", "order": "DESCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}
```

Define a sparse-any index:

```json
{
  "indexes": [
    {
      "collectionGroup": "cities",
      "queryScope": "COLLECTION",
      "density": "SPARSE_ANY",
      "fields": [
        { "fieldPath": "country", "order": "ASCENDING" },
        { "fieldPath": "population", "order": "DESCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}
```

Define a unique index:

```json
{
  "indexes": [
    {
      "collectionGroup": "cities",
      "queryScope": "COLLECTION",
      "density": "SPARSE_ANY",
      "unique": true,
      "fields": [
        { "fieldPath": "country", "order": "ASCENDING" },
        { "fieldPath": "population", "order": "DESCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}
```

### CLI Commands

Deploy indexes only:

```bash
npx -y firebase-tools@latest deploy --only firestore:indexes
```
references/enterprise/ios_setup.md›
# Firestore Enterprise Native Mode on iOS

This guide walks you through using the Cloud Firestore SDK in your iOS app using
Swift.

# ⛔️ 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()` or `Firestore.firestore(database:)`
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. **CRITICAL**: Enterprise databases
require a custom database ID and cannot use the `(default)` instance.

```swift
// Replace "your-enterprise-database-id" with your actual database ID
let db = Firestore.firestore(database: "your-enterprise-database-id")
```

## 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. Basic CRUD Operations

The operations are identical to standard Firestore, but ensure you use the `db`
instance initialized with your Enterprise database ID.

### Writing Data (Modern Concurrency & Codable)

```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)")
}
```

### 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)")
}
```

## 4. Pipeline Queries

Firestore Enterprise supports Pipeline operations for complex queries.

### Initialization

```swift
let pipeline = db.pipeline()
```

### Examples

```swift
// Return all documents across all collections in the database
let results = try await db.pipeline().database().execute()

// Filtered query
let results = try await db.pipeline()
    .collection("cities")
    .where(Field("name").equal(Constant("Toronto")))
    .execute()

// Compound query
let results = try await db.pipeline()
    .collection("books")
    .where(Field("rating").equal(5) && Field("published").lessThan(1900))
    .execute()
```

## 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
        // Note: Using the global default instance here, make sure to use your enterprise instance if applicable
        // For enterprise, you might need to pass the db instance or use a shared manager.
        listenerHandle = Firestore.firestore(database: "your-enterprise-database-id").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()
    }
}
```
references/enterprise/provisioning.md›
# Provisioning Firestore Enterprise Native Mode

## Manual Initialization

Initialize the following firebase configuration files manually. Do not use
`npx -y firebase-tools@latest init`, as it expects interactive inputs.

1. **Create a Firestore Enterprise Database**: Create a Firestore Enterprise
   database using the Firebase CLI.
1. **Create `firebase.json`**: This file contains database configuration for the
   Firebase CLI.
1. **Create `firestore.rules`**: This file contains your security rules.
1. **Create `firestore.indexes.json`**: This file contains your index
   definitions.

### 1. Create a Firestore Enterprise Database

If the user needs to create a new database, ask the user what location to use.
Run `npx -y firebase-tools@latest firestore:locations` to get the list of
options. Suggest colocating with other resources if applicable.

Use the following command to create a Firestore Enterprise database:

```bash
firebase firestore:databases:create my-database-id \
  --location="<selected-location>" \
  --edition="enterprise" \
  --firestore-data-access="ENABLED" \
  --mongodb-compatible-data-access="DISABLED"
```

This will create an enterprise database in the selected location with native
mode enabled. A database id is required to create an enterprise database and the
database id must not be `(default)`. To enable realtime-updates feature, use
`--realtime-updates` flag.

```bash
firebase firestore:databases:create my-database-id \
  --location="<selected-location>" \
  --edition="enterprise" \
  --firestore-data-access="ENABLED" \
  --mongodb-compatible-data-access="DISABLED" \
  --realtime-updates="ENABLED"
```

### 2. Create `firebase.json`

Create a file named `firebase.json` in your project root with the following
content (edit `database` and `location` to match the ones you created above). If
this file already exists, instead append to the existing JSON:

```json
{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json",
    "edition": "enterprise",
    "database": "my-database-id",
    "location": "<selected-location>"
  }
}
```

### 2. Create `firestore.rules`

Create a file named `firestore.rules`. A good starting point (locking down the
database) is:

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}
```

*See the `firestore-rules-creation` skill for how to write actual rules.*

### 3. Create `firestore.indexes.json`

Create a file named `firestore.indexes.json` with an empty configuration to
start:

```json
{
  "indexes": [],
  "fieldOverrides": []
}
```

*See [indexes.md](indexes.md) for how to configure indexes.*

## Deploy rules and indexes

```bash
# To deploy all rules and indexes
firebase deploy --only firestore

# To deploy just rules
firebase deploy --only firestore:rules

# To deploy just indexes
firebase deploy --only firestore:indexes
```

## Local Emulation

To run Firestore locally for development and testing:

```bash
firebase emulators:start --only firestore
```

This starts the Firestore emulator, typically on port 8080. You can interact
with it using the Emulator UI (usually at http://localhost:4000/firestore).
references/enterprise/python_sdk_usage.md›
# Python SDK Usage

The Python Server SDK is used for backend/server environments and utilizes
Google Application Default Credentials in most Google Cloud environments.

### Writing Data

#### Set a Document

Creates a document if it does not exist or overwrites it if it does. You can
also specify a merge option to only update provided fields.

```python
from google.cloud import firestore

db = firestore.Client(database="<database-id>")
city_ref = db.collection("cities").document("LA")

# Create/Overwrite
city_ref.set({
    "name": "Los Angeles",
    "state": "CA",
    "country": "USA"
})

# Merge
city_ref.set({"population": 3900000}, merge=True)
```

#### Add a Document with Auto-ID

Use when you don't care about the document ID and want Firestore to
automatically generate one.

```python
update_time, city_ref = db.collection("cities").add({
    "name": "Tokyo",
    "country": "Japan"
})
print("Document written with ID: ", city_ref.id)
```

#### Update a Document

Update some fields of an existing document without overwriting the entire
document. Fails if the document doesn't exist.

```python
city_ref = db.collection("cities").document("LA")
city_ref.update({
    "capital": True
})
```

#### Transactions

Perform an atomic read-modify-write operation.

```python
from google.cloud import firestore
from google.cloud.firestore import Transaction

transaction = db.transaction()
city_ref = db.collection("cities").document("SF")

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    if not snapshot.exists:
        raise Exception("Document does not exist!")

    new_population = snapshot.get("population") + 1
    transaction.update(city_ref, {"population": new_population})

update_in_transaction(transaction, city_ref)
```

### Reading Data

#### Get a Single Document

```python
doc_ref = db.collection("cities").document("SF")
doc = doc_ref.get()

if doc.exists:
    print(f"Document data: {doc.to_dict()}")
else:
    print("No such document!")
```

#### Get Multiple Documents

Fetches all documents in a query or collection once.

```python
docs = db.collection("cities").stream()

for doc in docs:
    print(f"{doc.id} => {doc.to_dict()}")
```

### Queries

#### Simple and Compound Queries

Use `.where()` to combine filters safely. Stack `.where()` calls for compound
queries.

```python
from google.cloud.firestore import FieldFilter

cities_ref = db.collection("cities")

# Simple equality
query_1 = cities_ref.where(filter=FieldFilter("state", "==", "CA"))

# Compound (AND)
query_2 = cities_ref.where(
    filter=FieldFilter("state", "==", "CA")
).where(
    filter=FieldFilter("population", ">", 1000000)
)
```

#### Order and Limit

Sort and limit results cleanly.

```python
query = cities_ref.order_by("name").limit(3)
```

#### Pipeline Queries

You can use pipeline queries to perform complex queries.

```python
pipeline = db.pipeline().collection("users")
for result in pipeline.execute():
    print(f"{result.id} => {result.data()}")
```
references/enterprise/web_sdk_usage.md›
# Web SDK Usage (Enterprise Native Mode)

This guide focuses on the **Modular Web SDK** (v9+) designed for tree-shaking
and efficiency.

______________________________________________________________________

## 1. Initialization

Initialize the SDK and Firestore instance:

```javascript
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  // Retrieve values using 'firebase apps:sdkconfig web <app-id>'
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app, "<database-id>");
```

______________________________________________________________________

## 2. Decision Framework: Pipelines vs. Standard Queries

> [!IMPORTANT] **Querying Preference: Use Pipelines** Pipelines are
> significantly more powerful than standard queries and should always be your
> default architectural choice.

### Rules & Accountability

1. **Check API References:** Always read `pipelines.d.ts` in
   `node_modules/@firebase/firestore/dist/` before querying.
1. **Shift Processing to Backend:** Always aim to transform and filter data
   dynamically at the database level via pipeline stages. Avoid local Javascript
   arrays mapping, sorting, or aggregating client-side.
1. **Reverting to Standard Queries:** Standard `collection.where()` queries and
   real-time listeners are an EXCEPTION. Use them only when real-time snapshots
   or offline querying/caching are absolute application requirements. When doing
   so, explicitly document the reason in your response.

______________________________________________________________________

## 3. Pipeline Examples

### Relational Joins Pattern

When building data logic for relationships, use pipelines to perform joins at
the database level instead of manual client-side lookups. - Use `.define()` to
bind alias parameters. - Invoke `.addFields()` incorporating a new subquery
linking the documents.

```javascript
import { execute, field, variable } from "firebase/firestore/pipelines";

// Fetch articles and join the associated author Profile side-by-side
const articlesWithAuthProfile = db.pipeline().collection("articles")
  .define(field("authorUid").as("author_id"))
  .addFields(
    db.pipeline().collection("users")
      .where(field("__name__").documentId().equal(variable("author_id")))
      .select(field("displayName"), field("avatarUrl"), field("handle"))
      .toScalarExpression()
      .as("author")
  );
const snapshot = await execute(articlesWithAuthProfile);
```

### Full-Text Search

Leverage the database-native `.search()` stage for high-performance text
lookups.

```javascript
import { documentMatches, execute, score } from "firebase/firestore/pipelines";
// Execute full-text search within pipeline
const searchPipeline = db.pipeline()
  .collection("articles")
  .search({
    query: documentMatches("machine learning"),
    sort: score().descending()
  })
  .limit(5);
const results = await execute(searchPipeline);
```

______________________________________________________________________

## 4. Real-Time Listener & Document Operations

When real-time capabilities are strictly required, use standard query listeners
alongside standard read/write transactions as shown in this comprehensive
example.

```javascript
import { collection, query, where, onSnapshot, doc, setDoc, updateDoc, addDoc } from "firebase/firestore";

// 1. Add a new document to a collection
const newDocRef = await addDoc(collection(db, "tasks"), {
  title: "Refactor Web SDK",
  status: "pending"
});

// 2. Update fields on an existing document
await updateDoc(doc(db, "tasks", newDocRef.id), {
  priority: "high"
});

// 3. Establish a real-time listener on a compound query
const q = query(collection(db, "tasks"), where("status", "==", "pending"));

const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === "added") {
        console.log("Added Task: ", change.doc.id, change.doc.data());
    }
    if (change.type === "modified") {
        console.log("Updated Task: ", change.doc.id, change.doc.data());
    }
    if (change.type === "removed") {
        console.log("Removed Task: ", change.doc.id, change.doc.data());
    }
  });
});
```
references/standard/android_sdk_usage.md›
# Cloud Firestore (Standard edition) - Android Setup Guide (Kotlin)

This guide describes the SDK setup and basic usage patterns for Cloud Firestore
(Standard edition) in an Android app using Kotlin DSL (`build.gradle.kts`) and
Kotlin code.

## Prerequisites

IMPORTANT: Before specifically working with Cloud Firestore, make sure to use
the skill and reference `firebase_basics/references/android_setup` to ensure the
following is done.

- The Firebase CLI is available and authenticated.
- An Android project exists and is registered with a Firebase Project.
- The Android project has a Firebase config file (`google-services.json`) and
  the Google services Gradle plugin (`google-services`).

______________________________________________________________________

## 1. Provision Firestore

Follow the instructions in `firebase_firestore/references/standard/provisioning`
to do the following:

- Provision a Firestore instance (Standard edition)
- Create or append a `firebase.json` file
- Create a `firestore.rules` file
- Create a `firestore.indexes.json` file
- Deploy database, rules, and indexes
- Run Firestore locally for development and testing

______________________________________________________________________

## 2. Add dependencies to Gradle build files

> [!IMPORTANT] **[AGENT] RESOLVING THE SDK VERSION DETERMINISTICALLY** Never
> guess or hardcode a memorized out-of-date SDK or plugin version. Before adding
> dependencies, you MUST query the latest available versions directly from the
> Google Maven Repository:
>
> ```bash
> # Find the latest Firebase Android BoM version
> curl -s https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-bom/maven-metadata.xml | grep -oE '<latest>[^<]+' | cut -d'>' -f2
>
> # Find the latest firebase-firestore version
> curl -s https://dl.google.com/dl/android/maven2/com/google/firebase/firebase-firestore/maven-metadata.xml | grep -oE '<latest>[^<]+' | cut -d'>' -f2
> ```
>
> If the dependency is already declared, fetch the exact resolved version
> currently active in the workspace's build graph:
>
> ```bash
> ./gradlew -q :app:dependencyInsight --dependency firebase-firestore --configuration releaseRuntimeClasspath
> ```

In the **module (app-level)** `build.gradle.kts` (usually
`<project>/<app-module>/build.gradle.kts`), add the dependency for Cloud
Firestore:

```kotlin
dependencies {
    // [AGENT] Fetch the latest resolved version using the Gradle command above
    implementation(platform("com.google.firebase:firebase-bom:<latest_bom_version>"))

    // Add the dependency for the Cloud Firestore library
    // When using the BoM, don't specify versions in Firebase library dependencies
    implementation("com.google.firebase:firebase-firestore")
}
```

______________________________________________________________________

## 3. Initialize Firestore

In the Activity or Fragment, initialize the `FirebaseFirestore` instance:

```kotlin
import com.google.firebase.Firebase
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.firestore

class MainActivity : AppCompatActivity() {

    private lateinit var db: FirebaseFirestore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        db = Firebase.firestore

        setContent {
            MaterialTheme {
                Text("Firestore initialized!")
            }
        }
    }
}
```

### Jetpack Compose (Modern)

Initialize inside a `ComponentActivity` using `setContent`:

```kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import com.google.firebase.Firebase
import com.google.firebase.firestore.firestore

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val db = Firebase.firestore

        setContent {
            MaterialTheme {
                Text("Firestore initialized!")
            }
        }
    }
}
```

______________________________________________________________________

## 4. Work with data

After Firestore is initialized, you can work with data in the following ways.

### Add data

Add a new document with a generated ID using `add()`:

```kotlin
// Create a new user with a first and last name
val user = hashMapOf(
    "first" to "Ada",
    "last" to "Lovelace",
    "born" to 1815
)

// Add a new document with a generated ID
db.collection("users")
    .add(user)
    .addOnSuccessListener { documentReference ->
        Log.d(TAG, "DocumentSnapshot added with ID: ${documentReference.id}")
    }
    .addOnFailureListener { e ->
        Log.w(TAG, "Error adding document", e)
    }
```

Or set a document with a specific ID using `set()`:

```kotlin
val city = hashMapOf(
    "name" to "Los Angeles",
    "state" to "CA",
    "country" to "USA"
)

db.collection("cities").document("LA")
    .set(city)
    .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
    .addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }
```

### Read data

Read a single document using `get()`:

```kotlin
val docRef = db.collection("cities").document("SF")
docRef.get()
    .addOnSuccessListener { document ->
        if (document != null && document.exists()) {
            Log.d(TAG, "DocumentSnapshot data: ${document.data}")
        } else {
            Log.d(TAG, "No such document")
        }
    }
    .addOnFailureListener { exception ->
        Log.d(TAG, "get failed with ", exception)
    }
```

Read multiple documents using a query:

```kotlin
db.collection("cities")
    .whereEqualTo("capital", true)
    .get()
    .addOnSuccessListener { documents ->
        for (document in documents) {
            Log.d(TAG, "${document.id} => ${document.data}")
        }
    }
    .addOnFailureListener { exception ->
        Log.w(TAG, "Error getting documents: ", exception)
    }
```

### Update data

Update some fields of a document using `update()` without overwriting the entire
document:

```kotlin
val washingtonRef = db.collection("cities").document("DC")

// Set the "isCapital" field to true
washingtonRef
    .update("capital", true)
    .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully updated!") }
    .addOnFailureListener { e -> Log.w(TAG, "Error updating document", e) }
```

### Delete data

Delete a document using `delete()`:

```kotlin
db.collection("cities").document("DC")
    .delete()
    .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully deleted!") }
    .addOnFailureListener { e -> Log.w(TAG, "Error deleting document", e) }
```
references/standard/flutter_setup.md›
# Cloud Firestore in Flutter

This guide covers basic CRUD operations, type-safe data modeling, and real-time
streams when using Cloud Firestore in a Flutter application via the
`cloud_firestore` package.

## 1. Setup

Ensure you have added the required dependency:

```bash
flutter pub add cloud_firestore
```

Also, ensure FlutterFire is configured properly for your target platforms.

______________________________________________________________________

## 2. Best Practices: Type-Safe Models

Instead of passing raw `Map<String, dynamic>` maps throughout your UI layer,
define a domain model class with `fromFirestore` and `toFirestore` converters to
maintain type safety.

```dart
import 'package:cloud_firestore/cloud_firestore.dart';

class Item {
  final String id;
  final String name;
  final String ownerId;
  final DateTime createdAt;

  Item({
    required this.id,
    required this.name,
    required this.ownerId,
    required this.createdAt,
  });

  factory Item.fromFirestore(DocumentSnapshot doc) {
    final data = doc.data() as Map<String, dynamic>? ?? {};
    return Item(
      id: doc.id,
      name: data['name'] as String? ?? '',
      ownerId: data['ownerId'] as String? ?? '',
      createdAt: data['createdAt'] is Timestamp 
          ? (data['createdAt'] as Timestamp).toDate() 
          : DateTime.now(),
    );
  }

  Map<String, dynamic> toFirestore() {
    return {
      'name': name,
      'ownerId': ownerId,
      'createdAt': Timestamp.fromDate(createdAt),
    };
  }
}
```

______________________________________________________________________

## 3. The Service Layer

Encapsulate all database interactions within a dedicated service class to keep
your UI code clean and testable.

### Initialization & References

```dart
class ItemService {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  // Define your collection reference
  CollectionReference get _itemsRef => _db.collection('items');

  // 1. Create Data
  Future<void> createItem(Item item) async {
    try {
      await _itemsRef.add(item.toFirestore());
    } catch (e) {
      print("Error creating document: $e");
    }
  }

  // 2. Read Data (One-Time Fetch)
  Future<List<Item>> fetchItems(String ownerId) async {
    try {
      final querySnapshot = await _itemsRef
          .where('ownerId', isEqualTo: ownerId)
          .orderBy('createdAt', descending: true)
          .get();

      return querySnapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
    } catch (e) {
      print("Error fetching documents: $e");
      return [];
    }
  }

  // 3. Read Data (Real-Time Stream)
  Stream<List<Item>> streamItems(String ownerId) {
    return _itemsRef
        .where('ownerId', isEqualTo: ownerId)
        .snapshots()
        .map((snapshot) {
          // If a custom composite index is missing during prototyping, apply sorting client-side:
          final items = snapshot.docs.map((doc) => Item.fromFirestore(doc)).toList();
          items.sort((a, b) => b.createdAt.compareTo(a.createdAt));
          return items;
        });
  }

  // 4. Update Data
  Future<void> updateItemName(String id, String newName) async {
    try {
      await _itemsRef.doc(id).update({'name': newName});
    } catch (e) {
      print("Error updating document: $e");
    }
  }

  // 5. Delete Data
  Future<void> deleteItem(String id) async {
    try {
      await _itemsRef.doc(id).delete();
    } catch (e) {
      print("Error deleting document: $e");
    }
  }
}
```

______________________________________________________________________

## 4. Listening to Streams in the UI (`StreamBuilder`)

Use Flutter's `StreamBuilder` to rebuild the interface reactively whenever data
changes in your database collection.

```dart
StreamBuilder<List<Item>>(
  stream: itemService.streamItems(currentUser.uid),
  builder: (context, snapshot) {
    if (snapshot.hasError) {
      return const Center(child: Text('Failed to load data'));
    }

    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(child: CircularProgressIndicator());
    }

    final items = snapshot.data ?? [];

    if (items.isEmpty) {
      return const Center(child: Text('No items found.'));
    }

    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return ListTile(
          title: Text(item.name),
          trailing: IconButton(
            icon: const Icon(Icons.delete),
            onPressed: () => itemService.deleteItem(item.id),
          ),
        );
      },
    );
  },
);
```
references/standard/indexes.md›
# Firestore Indexes Reference

Indexes allow Firestore to ensure that query performance depends on the size of
the result set, not the size of the database.

## Index Types

### Single-Field Indexes

In Standard Edition, Firestore **automatically creates** a single-field index
for every field in a document (and subfields in maps). * **Support**: Simple
equality queries (`==`) and single-field range/sort queries (`<`, `<=`,
`orderBy`). * **Behavior**: You generally don't need to manage these unless you
want to *exempt* a field.

### Composite Indexes

A composite index stores a sorted mapping of all documents based on an ordered
list of fields. * **Support**: Complex queries that filter or sort by **multiple
fields**. * **Creation**: These are **NOT** automatically created. You must
define them manually or via the console/CLI.

## Automatic vs. Manual Management

### What is Automatic?

- Indexes for simple queries.
- Merging of single-field indexes for multiple equality filters (e.g.,
  `where("state", "==", "CA").where("country", "==", "USA")`).

### When Do I Need to Act?

If you attempt a query that requires a composite index, the SDK will throw an
error containing a **direct link** to the Firebase Console to create that
specific index.

**Example Error:**

> "The query requires an index. You can create it here:
> https://console.firebase.google.com/project/..."

## Query Support Examples

- **Simple Equality**: `where("a", "==", 1)`
  - Index Required: Automatic (Single-Field)
- **Simple Range/Sort**: `where("a", ">", 1).orderBy("a")`
  - Index Required: Automatic (Single-Field)
- **Multiple Equality**: `where("a", "==", 1).where("b", "==", 2)`
  - Index Required: Automatic (Merged Single-Field)
- **Equality + Range/Sort**: `where("a", "==", 1).where("b", ">", 2)`
  - Index Required: **Composite Index**
- **Multiple Ranges**: `where("a", ">", 1).where("b", ">", 2)`
  - Index Required: **Composite Index** (limited query support)
- **Array Contains + Equality**:
  `where("tags", "array-contains", "news").where("active", "==", true)`
  - Index Required: **Composite Index**

## Best Practices & Exemptions

You can **exempt** fields from automatic indexing to save storage or strictly
enforce write limits.

### 1. High Write Rates (Sequential Values)

- **Problem**: Indexing fields that increase sequentially (like `timestamp`)
  limits the write rate to ~500 writes/second per collection.
- **Solution**: If you don't query on this field, **exempt** it from simple
  indexing.

### 2. Large String/Map/Array Fields

- **Problem**: Indexing limits (40k entries per doc). Indexing large blobs
  wastes storage.
- **Solution**: Exempt large text blobs or huge arrays if they aren't used for
  filtering.

### 3. TTL Fields

- **Problem**: TTL (Time-To-Live) deletion can cause index churn.
- **Solution**: Exempt the TTL timestamp field from indexing if you don't query
  it.

## Management

### Config files

Your indexes should be defined in `firestore.indexes.json` (pointed to by
`firebase.json`).

```json
{
  "indexes": [
    {
      "collectionGroup": "cities",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "country", "order": "ASCENDING" },
        { "fieldPath": "population", "order": "DESCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}
```

### CLI Commands

Deploy indexes only:

```bash
npx -y firebase-tools@latest deploy --only firestore:indexes
```
references/standard/ios_setup.md›
# 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()
            }
        }
    }
}
```
references/standard/provisioning.md›
# Provisioning Cloud Firestore

## Manual Initialization

Initialize the following firebase configuration files manually. Do not use
`npx -y firebase-tools@latest init`, as it expects interactive inputs.

1. **Create `firebase.json`**: This file configures the Firebase CLI.
1. **Create `firestore.rules`**: This file contains your security rules.
1. **Create `firestore.indexes.json`**: This file contains your index
   definitions.

### 1. Create `firebase.json`

Create a file named `firebase.json` in your project root with the following
content. If this file already exists, instead append to the existing JSON:

```json
{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  }
}
```

This will use the default database with the Standard edition. To use a different
database, specify the database ID and location:

1. Run `npx -y firebase-tools@latest firestore:locations` to get the list of
   locations.
1. Ask the user which location to use, suggesting colocation if other parts of
   the app already have a region selected.

You can check the list of available databases using
`npx -y firebase-tools@latest firestore:databases:list`.

If the database does not exist, it will be created when you deploy with the
specified configuration:

```json
{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json",
    "database": "my-database-id",
    "location": "<selected-location>"
  }
}
```

### 2. Create `firestore.rules`

Create a file named `firestore.rules`. A good starting point (locking down the
database) is:

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}
```

*See the `firestore-rules-creation` skill for how to write actual rules.*

### 3. Create `firestore.indexes.json`

Create a file named `firestore.indexes.json` with an empty configuration to
start:

```json
{
  "indexes": [],
  "fieldOverrides": []
}
```

*See [indexes.md](indexes.md) for how to configure indexes.*

## Deploy database, rules and indexes

**CRITICAL**: You MUST deploy the firestore configuration for the database to be
provisioned in the cloud and for your rules/indexes to take effect. If you don't
run this, your database will not exist.

```bash
# To deploy all rules and indexes
npx -y firebase-tools@latest deploy --only firestore

# To deploy just rules
npx -y firebase-tools@latest deploy --only firestore:rules

# To deploy just indexes
npx -y firebase-tools@latest deploy --only firestore:indexes
```

## Local Emulation

To run Firestore locally for development and testing:

```bash
npx -y firebase-tools@latest emulators:start --only firestore
```

This starts the Firestore emulator, typically on port 8080. You can interact
with it using the Emulator UI (usually at http://localhost:4000/firestore).
references/standard/web_sdk_usage.md›
# Firestore Web SDK Usage Guide

This guide focuses on the **Modular Web SDK** (v9+), which is tree-shakeable and
efficient.

## Initialization

```javascript
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

// If running in Firebase App Hosting, you can skip Firebase Config and instead use:
// const app = initializeApp();

const firebaseConfig = {
  // Your config options. Get the values by running 'npx -y firebase-tools@latest apps:sdkconfig <platform> <app-id>'
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

```

## Writing Data

### Set a Document (`setDoc`)

Creates a document if it doesn't exist, or overwrites it if it does.

```javascript
import { doc, setDoc } from "firebase/firestore";

// Create/Overwrite document with ID "LA"
await setDoc(doc(db, "cities", "LA"), {
  name: "Los Angeles",
  state: "CA",
  country: "USA"
});

// To merge with existing data instead of overwriting:
await setDoc(doc(db, "cities", "LA"), { population: 3900000 }, { merge: true });
```

### Add a Document with Auto-ID (`addDoc`)

Use when you don't care about the document ID.

```javascript
import { collection, addDoc } from "firebase/firestore";

const docRef = await addDoc(collection(db, "cities"), {
  name: "Tokyo",
  country: "Japan"
});
console.log("Document written with ID: ", docRef.id);
```

### Update a Document (`updateDoc`)

Update some fields of an existing document without overwriting the entire
document. Fails if the document doesn't exist.

```javascript
import { doc, updateDoc } from "firebase/firestore";

const laRef = doc(db, "cities", "LA");

await updateDoc(laRef, {
  capital: true
});
```

### Transactions

Perform an atomic read-modify-write operation.

```javascript
import { runTransaction, doc } from "firebase/firestore";

const sfDocRef = doc(db, "cities", "SF");

try {
  await runTransaction(db, async (transaction) => {
    const sfDoc = await transaction.get(sfDocRef);
    if (!sfDoc.exists()) {
      throw "Document does not exist!";
    }

    const newPopulation = sfDoc.data().population + 1;
    transaction.update(sfDocRef, { population: newPopulation });
  });
  console.log("Transaction successfully committed!");
} catch (e) {
  console.log("Transaction failed: ", e);
}
```

## Reading Data

### Get a Single Document (`getDoc`)

```javascript
import { doc, getDoc } from "firebase/firestore";

const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);

if (docSnap.exists()) {
  console.log("Document data:", docSnap.data());
} else {
  console.log("No such document!");
}
```

### Get Multiple Documents (`getDocs`)

Fetches all documents in a query or collection once.

```javascript
import { collection, getDocs } from "firebase/firestore";

const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});
```

## Realtime Updates

### Listen to a Document/Query (`onSnapshot`)

```javascript
import { doc, onSnapshot } from "firebase/firestore";

const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
    console.log("Current data: ", doc.data());
});

// Stop listening
// unsub();
```

### Handle Changes (Added/Modified/Removed)

```javascript
import { collection, query, where, onSnapshot } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === "added") {
        console.log("New city: ", change.doc.data());
    }
    if (change.type === "modified") {
        console.log("Modified city: ", change.doc.data());
    }
    if (change.type === "removed") {
        console.log("Removed city: ", change.doc.data());
    }
  });
});
```

## Queries

### Simple and Compound Queries

Use `query()` to combine filters.

```javascript
import { collection, query, where, getDocs } from "firebase/firestore";

const citiesRef = collection(db, "cities");

// Simple equality
const q1 = query(citiesRef, where("state", "==", "CA"));

// Compound (AND)
// Note: Requires an index if filtering on different fields
const q2 = query(citiesRef, where("state", "==", "CA"), where("population", ">", 1000000));
```

### Order and Limit

Sort and limit results.

```javascript
import { orderBy, limit } from "firebase/firestore";

const q = query(citiesRef, orderBy("name"), limit(3));
```
SKILL.md›
---
name: firebase-firestore
description: >-
  Sets up, manages, queries, and configures Cloud Firestore databases (Standard/Enterprise edition), including data modeling, security rules, indexes, and SDK integrations (Web, Python, iOS, Android, Flutter). Use when creating/listing Firestore databases, defining data models/indexes, writing SDK queries, or integrating Firestore SDKs. For authoring or modifying Firestore Security Rules (firestore.rules), delegate to the firestore-rules-author subagent if subagent delegation is available, or use firestore-rules-creation otherwise. Don't use for Firebase Hosting, Data Connect, Auth, Storage/GCS, Crashlytics, Functions, or BigQuery.
compatibility: This skill is best used with the Firebase CLI, but does not require it. Firebase CLI can be accessed through `npx -y firebase-tools@latest`.
metadata:
  author: Google LLC
  category: Databases
---

# Cloud Firestore Database and Operations

> [!IMPORTANT] **Security Rules Authoring (`firestore.rules`)** Whenever your
> task requires creating, authoring, or modifying `firestore.rules`:
>
> - **If subagent delegation AND the `firestore-rules-author` subagent are
>   available**: delegate authoring `firestore.rules` to the
>   `firestore-rules-author` subagent rather than writing `firestore.rules`
>   directly in the main agent.
> - **If subagent delegation is unavailable** (e.g., subagents not enabled in
>   the IDE) **OR `firestore-rules-author` is not installed**: read and follow
>   the `firestore-rules-creation` skill to write `firestore.rules` directly.

Before setting up dependencies, writing data models, or configuring security
rules, you MUST always identify the Firestore instance edition.

## 1. Instance Selection and Edition Detection

Run the following command to list current Firestore databases:

```bash
npx -y firebase-tools@latest firestore:databases:list
```

### A. Instance Found

1. For each database found, inspect its edition and details:

   ```bash
   npx -y firebase-tools@latest firestore:databases:get <database-id>
   ```

1. Ask the user which database instance they wish to target or if they would
   prefer to create a new instance.

1. Once the target instance is established:

   - If the **`edition`** is `STANDARD`, follow the guides under
     `references/standard/`.
   - If the **`edition`** is `ENTERPRISE` or native mode, follow the guides
     under `references/enterprise/`.

### B. No Instance Found (or New Requested)

If no databases exist or the user requests a new one, default to provisioning an
**Enterprise** edition database and ask the user what location to use. Run
`npx -y firebase-tools@latest firestore:locations` to get the list of options.
Suggest colocating with other resources if applicable.

Once the location is determined, create the database:

```bash
npx -y firebase-tools@latest firestore:databases:create <database-id> --edition="enterprise" --location="<selected-location>"
```

Proceed with using the guides under `references/enterprise/`.

______________________________________________________________________

## 2. Specialized Guides

Based on the identified or created instance edition, open and read the
corresponding reference guides:

### Standard Edition (`references/standard/`)

- **Provisioning**: Read [provisioning.md](references/standard/provisioning.md)
- **Security Rules**: See the `firestore-rules-creation` skill
- **SDK Usage**: Read [web_sdk_usage.md](references/standard/web_sdk_usage.md),
  [android_sdk_usage.md](references/standard/android_sdk_usage.md),
  [ios_setup.md](references/standard/ios_setup.md), or
  [flutter_setup.md](references/standard/flutter_setup.md)
- **Indexes**: Read [indexes.md](references/standard/indexes.md)

### Enterprise Edition / Native Mode (`references/enterprise/`)

- **Provisioning**: Read
  [provisioning.md](references/enterprise/provisioning.md)

- **Data Model**: Read [data_model.md](references/enterprise/data_model.md)

- **Security Rules**: See the `firestore-rules-creation` skill

- **SDK Usage**:

  > [!CRITICAL] **Mandatory Reference Reading** Before writing or modifying any
  > application code for Firestore Enterprise Edition, you **MUST** read at
  > least one of the relevant reference documents below for the target
  > platform/language to understand specific architectural requirements and
  > pipeline initialization patterns.

  Read [web_sdk_usage.md](references/enterprise/web_sdk_usage.md),
  [python_sdk_usage.md](references/enterprise/python_sdk_usage.md),
  [android_sdk_usage.md](references/enterprise/android_sdk_usage.md),
  [ios_setup.md](references/enterprise/ios_setup.md), or
  [flutter_setup.md](references/enterprise/flutter_setup.md)

- **Indexes**: Read [indexes.md](references/enterprise/indexes.md)