Flutter Firebase Skill
Setup
Install FlutterFire CLI
dart pub global activate flutterfire_cli
Configure Firebase
flutterfire configure
Core Dependencies
dependencies:
firebase_core: ^2.27.0
firebase_auth: ^4.17.0
cloud_firestore: ^4.15.0
firebase_storage: ^11.6.0
firebase_messaging: ^14.7.0
firebase_analytics: ^10.8.0
firebase_crashlytics: ^3.4.0
Initialize (main.dart)
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Optional: Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}
Firebase Auth
Email/Password Sign Up
Future<UserCredential> signUp(String email, String password) async {
return await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
}
Email/Password Sign In
Future<UserCredential> signIn(String email, String password) async {
return await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
}
Google Sign In
// Add: google_sign_in: ^6.2.1
Future<UserCredential> signInWithGoogle() async {
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
if (googleUser == null) throw Exception('Sign in cancelled');
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
return await FirebaseAuth.instance.signInWithCredential(credential);
}
Apple Sign In
// Add: sign_in_with_apple: ^5.0.0
Future<UserCredential> signInWithApple() async {
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
final oauthCredential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
accessToken: appleCredential.authorizationCode,
);
return await FirebaseAuth.instance.signInWithCredential(oauthCredential);
}
Sign Out
Future<void> signOut() async {
await FirebaseAuth.instance.signOut();
await GoogleSignIn().signOut(); // If using Google
}
Current User
User? get currentUser => FirebaseAuth.instance.currentUser;
Auth State Stream
@riverpod
Stream<User?> authState(Ref ref) {
return FirebaseAuth.instance.authStateChanges();
}
// Or with more detail
Stream<User?> userChanges() {
return FirebaseAuth.instance.userChanges();
}
Password Reset
Future<void> resetPassword(String email) async {
await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
}
Update Profile
Future<void> updateProfile({String? displayName, String? photoURL}) async {
await FirebaseAuth.instance.currentUser?.updateDisplayName(displayName);
await FirebaseAuth.instance.currentUser?.updatePhotoURL(photoURL);
}
Cloud Firestore
Get Collection
Future<List<Map<String, dynamic>>> getAll(String collection) async {
final snapshot = await FirebaseFirestore.instance
.collection(collection)
.get();
return snapshot.docs.map((doc) => {
'id': doc.id,
...doc.data(),
}).toList();
}
Get Document
Future<Map<String, dynamic>?> getById(String collection, String id) async {
final doc = await FirebaseFirestore.instance
.collection(collection)
.doc(id)
.get();
if (!doc.exists) return null;
return {'id': doc.id, ...doc.data()!};
}
Query with Filters
Future<List<Map<String, dynamic>>> query(
String collection, {
required String field,
required dynamic value,
}) async {
final snapshot = await FirebaseFirestore.instance
.collection(collection)
.where(field, isEqualTo: value)
.orderBy('createdAt', descending: true)
.limit(20)
.get();
return snapshot.docs.map((doc) => {
'id': doc.id,
...doc.data(),
}).toList();
}
Add Document (Auto ID)
Future<String> add(String collection, Map<String, dynamic> data) async {
final doc = await FirebaseFirestore.instance
.collection(collection)
.add({
...data,
'createdAt': FieldValue.serverTimestamp(),
});
return doc.id;
}
Set Document (Custom ID)
Future<void> set(String collection, String id, Map<String, dynamic> data) async {
await FirebaseFirestore.instance
.collection(collection)
.doc(id)
.set({
...data,
'updatedAt': FieldValue.serverTimestamp(),
});
}
Update Document
Future<void> update(String collection, String id, Map<String, dynamic> data) async {
await FirebaseFirestore.instance
.collection(collection)
.doc(id)
.update({
...data,
'updatedAt': FieldValue.serverTimestamp(),
});
}
Delete Document
Future<void> delete(String collection, String id) async {
await FirebaseFirestore.instance
.collection(collection)
.doc(id)
.delete();
}
Realtime Stream (Single Doc)
Stream<Map<String, dynamic>?> watchDocument(String collection, String id) {
return FirebaseFirestore.instance
.collection(collection)
.doc(id)
.snapshots()
.map((doc) => doc.exists ? {'id': doc.id, ...doc.data()!} : null);
}
Realtime Stream (Collection)
Stream<List<Map<String, dynamic>>> watchCollection(String collection) {
return FirebaseFirestore.instance
.collection(collection)
.orderBy('createdAt', descending: true)
.snapshots()
.map((snapshot) => snapshot.docs.map((doc) => {
'id': doc.id,
...doc.data(),
}).toList());
}
Batch Write
Future<void> batchWrite(List<Map<String, dynamic>> items) async {
final batch = FirebaseFirestore.instance.batch();
final collection = FirebaseFirestore.instance.collection('prayers');
for (final item in items) {
batch.set(collection.doc(), item);
}
await batch.commit();
}
Transaction
Future<void> transfer(String fromId, String toId, int amount) async {
await FirebaseFirestore.instance.runTransaction((transaction) async {
final fromDoc = FirebaseFirestore.instance.collection('accounts').doc(fromId);
final toDoc = FirebaseFirestore.instance.collection('accounts').doc(toId);
final fromSnapshot = await transaction.get(fromDoc);
final toSnapshot = await transaction.get(toDoc);
final fromBalance = fromSnapshot.data()!['balance'] as int;
final toBalance = toSnapshot.data()!['balance'] as int;
transaction.update(fromDoc, {'balance': fromBalance - amount});
transaction.update(toDoc, {'balance': toBalance + amount});
});
}
Query Operators
// Equal
.where('field', isEqualTo: value)
// Not equal
.where('field', isNotEqualTo: value)
// Greater than
.where('field', isGreaterThan: value)
// Less than
.where('field', isLessThan: value)
// Array contains
.where('tags', arrayContains: 'ramadan')
// Array contains any
.where('tags', arrayContainsAny: ['ramadan', 'fasting'])
// In
.where('status', whereIn: ['active', 'pending'])
// Not in
.where('status', whereNotIn: ['deleted', 'archived'])
Firebase Storage
Upload File
Future<String> uploadFile(String path, File file) async {
final ref = FirebaseStorage.instance.ref().child(path);
await ref.putFile(file);
return await ref.getDownloadURL();
}
Upload Bytes
Future<String> uploadBytes(String path, Uint8List bytes) async {
final ref = FirebaseStorage.instance.ref().child(path);
await ref.putData(bytes);
return await ref.getDownloadURL();
}
Download URL
Future<String> getDownloadUrl(String path) async {
return await FirebaseStorage.instance.ref().child(path).getDownloadURL();
}
Delete File
Future<void> deleteFile(String path) async {
await FirebaseStorage.instance.ref().child(path).delete();
}
Upload with Progress
Stream<double> uploadWithProgress(String path, File file) {
final ref = FirebaseStorage.instance.ref().child(path);
final task = ref.putFile(file);
return task.snapshotEvents.map((snapshot) {
return snapshot.bytesTransferred / snapshot.totalBytes;
});
}
Cloud Messaging (FCM)
Setup
Future<void> setupFCM() async {
final messaging = FirebaseMessaging.instance;
// Request permission (iOS)
await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
// Get token
final token = await messaging.getToken();
print('FCM Token: $token');
// Listen for token refresh
messaging.onTokenRefresh.listen((token) {
// Save to server
});
}
Handle Messages
void setupMessageHandlers() {
// Foreground messages
FirebaseMessaging.onMessage.listen((message) {
print('Foreground: ${message.notification?.title}');
// Show local notification
});
// Background messages (app open)
FirebaseMessaging.onMessageOpenedApp.listen((message) {
print('Opened from background: ${message.data}');
// Navigate to screen
});
}
// Background handler (must be top-level function)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Background message: ${message.messageId}');
}
// Register in main()
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
Subscribe to Topic
Future<void> subscribeToTopic(String topic) async {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
Future<void> unsubscribeFromTopic(String topic) async {
await FirebaseMessaging.instance.unsubscribeFromTopic(topic);
}
Analytics
final analytics = FirebaseAnalytics.instance;
// Log event
await analytics.logEvent(
name: 'prayer_completed',
parameters: {
'prayer_name': 'fajr',
'on_time': true,
},
);
// Log screen
await analytics.logScreenView(
screenName: 'HomeScreen',
screenClass: 'HomePage',
);
// Set user property
await analytics.setUserProperty(
name: 'preferred_language',
value: 'urdu',
);
// Set user ID
await analytics.setUserId(id: userId);
Crashlytics
final crashlytics = FirebaseCrashlytics.instance;
// Log error
await crashlytics.recordError(
error,
stackTrace,
reason: 'Payment failed',
fatal: false,
);
// Set user ID
await crashlytics.setUserIdentifier(userId);
// Custom key
await crashlytics.setCustomKey('screen', 'checkout');
// Log message
await crashlytics.log('User clicked checkout button');
Error Handling Pattern
Future<Either<Failure, User>> getUser(String id) async {
try {
final doc = await FirebaseFirestore.instance
.collection('users')
.doc(id)
.get();
if (!doc.exists) {
return Left(NotFoundFailure(message: 'User not found'));
}
return Right(UserModel.fromJson({
'id': doc.id,
...doc.data()!,
}).toEntity());
} on FirebaseException catch (e) {
return Left(FirebaseFailure(message: e.message ?? 'Firebase error'));
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
Repository Pattern
class PrayerRemoteDataSource {
final FirebaseFirestore _firestore;
PrayerRemoteDataSource(this._firestore);
Future<List<PrayerModel>> getAll() async {
final snapshot = await _firestore
.collection('prayers')
.orderBy('time')
.get();
return snapshot.docs
.map((doc) => PrayerModel.fromJson({'id': doc.id, ...doc.data()}))
.toList();
}
Stream<List<PrayerModel>> watchAll() {
return _firestore
.collection('prayers')
.orderBy('time')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => PrayerModel.fromJson({'id': doc.id, ...doc.data()}))
.toList());
}
}
Common Mistakes
// ❌ Not awaiting Firebase.initializeApp()
void main() {
Firebase.initializeApp(); // Missing await!
runApp(MyApp());
}
// ✅ Correct
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(MyApp());
}
// ❌ Using .get() when you need realtime
final data = await FirebaseFirestore.instance.collection('x').get();
// ✅ Use .snapshots() for realtime
final stream = FirebaseFirestore.instance.collection('x').snapshots();
// ❌ Not handling null document
final doc = await FirebaseFirestore.instance.collection('x').doc(id).get();
return doc.data()!; // Might crash!
// ✅ Handle null
if (!doc.exists) throw Exception('Not found');
return doc.data()!;
Converted and distributed by TomeVault — claim your Tome and manage your conversions.