Cloud Firestore
ShipFlutter uses Cloud Firestore as its database solution, providing a flexible, scalable NoSQL cloud database with offline support. The database module is part of the core Firebase integration, making it seamless to work with user data, settings, and app state.
Before starting, make sure you have completed the Firebase setup, including the Firestore setup steps.
Using FirebaseStore
The database functionality is implemented in the core/firebase/database
module, providing a type-safe wrapper around Firestore operations through the FirebaseStore
class:
final store = FirebaseStore<User>( root: "users", fromFirestore: (snapshot, options) { final data = snapshot.data()!; // You can add additional fields here if needed data["id"] = snapshot.id; return User.fromJson(data); }, toFirestore: (value, options) => value.toJson(),);
Configuration
- root: The path to your collection (e.g.,
users
orstore/1234/purchases
) - fromFirestore: Convert Firestore data to your model
- toFirestore: Convert your model to Firestore data
Key Features
// Observe collection with filtersstore.observeItems( filters: (query) => query.where('field', isEqualTo: 'value'), onlyChanges: true, // Skip initial cache data).listen((items) { // Handle items});
// Observe single documentstore.observeItem('documentId').listen((item) { // Handle item updates});
// Get collectionfinal items = await store.getItems();
// Get single documentfinal item = await store.getItem('documentId');
// Check if document existsfinal exists = await store.exists('documentId');
// Create with auto-generated IDfinal id = await store.createItem(newItem);
// Transform before creatingfinal id = await store.createItem( newItem, transform: (id) => newItem.copyWith(id: id),);
// Create with custom IDawait store.createItem(newItem, id: 'custom-id');
// Updateawait store.updateItem( id: 'documentId', item: updatedItem, merge: true,);
// Update specific fieldsawait store.setItemFields( id: 'documentId', fields: {'status': 'active'},);
// Deleteawait store.deleteItem('documentId');
await store.batch((collection, batch) { // Perform multiple operations atomically batch.set(...); batch.update(...); batch.delete(...);});
Security Rules
The firestore.rules
file at the root of your project defines access control:
rules_version = '2';service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth != null; } }}
Best Practices
-
Data Structure
- Keep documents small and flat
- Use subcollections for scalable relationships
- Follow the Firebase recommendations
-
Queries
- Create indexes for complex queries
- Use filters efficiently
- Limit result size when possible
- Cache frequently accessed data
-
Offline Support
- Enable persistence for offline access
- Handle offline/online state changes
- Use
onlyChanges
parameter to skip cached data - Implement retry logic for failed operations
Troubleshooting
Here are some common issues you might encounter:
iOS performance issues
For better performance on iOS, we use a pre-compiled version of Firestore by adding these lines to the Podfile
:
target 'Runner' do # Other podfile lines...
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '11.4.0'
# Other podfile lines...end
iOS build issues
Add these lines if you encounter iOS build issues:
target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'i386'end