# 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` 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? ?? {}; 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 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 createItem(Item item) async { try { await _itemsRef.add(item.toFirestore()); } catch (e) { print("Error creating document: $e"); } } // 2. Read Data (One-Time Fetch) Future> 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> 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 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 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>( 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), ), ); }, ); }, ); ```