Flutter in Production
We've shipped 4 Flutter apps to production: SmartHome, Ai-Home, Loan Manager, and Financial Analyzer mobile. Here are the key lessons.
Firestore Query Optimization
The #1 mistake we made early: unoptimized Firestore queries that blew through our free tier in days.
// BAD: Reads ALL documents
final snapshot = await FirebaseFirestore.instance
.collection('devices')
.get();
// GOOD: Paginated + filtered
final snapshot = await FirebaseFirestore.instance
.collection('devices')
.where('userId', isEqualTo: currentUser.uid)
.where('isActive', isEqualTo: true)
.orderBy('lastSeen', descending: true)
.limit(20)
.get();State Management: Riverpod
After trying Provider, Bloc, and GetX, we settled on Riverpod for all new projects:
@riverpod
class DeviceController extends _$DeviceController {
@override
Future<List<Device>> build() async {
final userId = ref.watch(currentUserProvider).value?.uid;
if (userId == null) return [];
return ref.watch(deviceRepositoryProvider).getDevices(userId);
}
Future<void> toggleDevice(String deviceId) async {
final repo = ref.read(deviceRepositoryProvider);
await repo.toggleDevice(deviceId);
ref.invalidateSelf();
}
}FCM Notification Delivery
Firebase Cloud Messaging has quirks on different Android OEMs:
Our solution: a comprehensive onboarding flow that guides users through device-specific settings.
Offline-First Architecture
SmartHome needs to work even when the internet is down (your lights shouldn't stop working):
class OfflineFirstRepository {
final FirebaseFirestore _firestore;
final SharedPreferences _prefs;
Future<List<Device>> getDevices(String userId) async {
try {
// Try network first
final snapshot = await _firestore
.collection('devices')
.where('userId', isEqualTo: userId)
.get(const GetOptions(source: Source.server));
// Cache locally
await _cacheDevices(snapshot.docs);
return _parseDevices(snapshot);
} catch (e) {
// Fallback to cache
return _getCachedDevices();
}
}
}