--- 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> 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