---
description: "Firebase conventions for {{PROJECT_NAME}}"
alwaysApply: true
---

# Firebase Standards — {{PROJECT_NAME}}

## Firestore
- Collection names: `camelCase` plural — `users`, `products`, `orderItems`
- Document IDs: use Firebase auto-IDs unless a natural key exists
- **Streams vs Futures**: use `snapshots()` for live data, `get()` for one-time reads
- Always handle `FirebaseException` explicitly — catch by `e.code` not generic `Exception`
- Paginate large collections with `startAfterDocument` — never fetch unbounded collections

```dart
// ✅ Stream-based real-time listener
Stream<List<Product>> watchProducts() {
  return _firestore.collection('products')
    .where('isActive', isEqualTo: true)
    .snapshots()
    .map((snap) => snap.docs.map(Product.fromDoc).toList());
}

// ✅ Handle FirebaseException by code
try {
  await _firestore.collection('orders').add(order.toMap());
} on FirebaseException catch (e) {
  switch (e.code) {
    case 'permission-denied': throw AppError.authError('Insufficient permissions');
    case 'unavailable':       throw AppError.networkError();
    default:                  throw AppError.unknown(e);
  }
}
```

## Firebase Auth
- Always use `authStateChanges()` stream — never cache auth state locally
- Handle all error codes: `user-not-found`, `wrong-password`, `email-already-in-use`, `network-request-failed`
- Sign-out: clear all local state AND call `FirebaseAuth.instance.signOut()`

## Cloud Functions
- Call via `FirebaseFunctions.instance.httpsCallable('functionName')`
- Handle `FirebaseFunctionsException` with `.code` and `.message`
- Never expose internal errors to client — functions return structured error responses

## Security (complement to security-standards.mdc)
- Firestore Security Rules must be tested with the emulator before deploying
- No `allow read, write: if true` — even in development
- Rule coverage: every collection must have explicit rules
