1. How do you set up Firebase in a Flutter project using FlutterFire CLI?
The FlutterFire CLI automates Firebase configuration for all platforms.
# Install FlutterFire CLI globally
dart pub global activate flutterfire_cli
# Login to Firebase
firebase login
# Configure Firebase for your Flutter project
flutterfire configureThis command:
- Creates a new Firebase project or uses existing one
- Registers your app for each platform (iOS, Android, Web)
- Generates
firebase_options.dartwith all configurations - Downloads necessary config files (
google-services.json,GoogleService-Info.plist)
After configuration, add the required packages:
dependencies:
firebase_core: ^2.24.0
# Add other Firebase packages as needed2. What is the purpose of firebase_options.dart and how is it generated?
firebase_options.dart is an auto-generated file that contains platform-specific Firebase configuration options.
// firebase_options.dart (generated)
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
// ...
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'your-api-key',
appId: 'your-app-id',
messagingSenderId: 'your-sender-id',
projectId: 'your-project-id',
storageBucket: 'your-bucket',
);
// Similar for iOS, web, etc.
}Generated by: flutterfire configure
Purpose:
- Centralizes all Firebase configurations
- Handles platform detection automatically
- Avoids manual configuration file management
3. How do you configure Firebase for multiple environments (development, staging, production) in Flutter?
Method 1: Multiple Firebase projects with flavor-based configuration
// lib/firebase_options_dev.dart
// lib/firebase_options_staging.dart
// lib/firebase_options_prod.dart
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FirebaseOptions options;
switch (appFlavor) {
case 'dev':
options = DevFirebaseOptions.currentPlatform;
break;
case 'staging':
options = StagingFirebaseOptions.currentPlatform;
break;
case 'prod':
default:
options = ProdFirebaseOptions.currentPlatform;
}
await Firebase.initializeApp(options: options);
runApp(MyApp());
}Method 2: Using environment variables
// Using --dart-define
// flutter run --dart-define=FIREBASE_ENV=dev
void main() async {
const env = String.fromEnvironment('FIREBASE_ENV', defaultValue: 'prod');
await Firebase.initializeApp(
options: getFirebaseOptions(env),
);
runApp(MyApp());
}4. What are the required changes in Android and iOS native files when adding Firebase to a Flutter app?
Android (android/app/build.gradle):
// Add at the bottom
apply plugin: 'com.google.gms.google-services'
android {
defaultConfig {
minSdkVersion 21 // Firebase requires minimum SDK 21
}
}Android (android/build.gradle):
dependencies {
classpath 'com.google.gms:google-services:4.4.0'
}iOS (ios/Podfile):
platform :ios, '12.0' # Minimum iOS version for FirebaseiOS (ios/Runner/Info.plist): For specific features like FCM:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>Note: FlutterFire CLI handles most of these automatically.
5. How do you initialize Firebase in a Flutter application?
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
// Ensure Flutter bindings are initialized
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}
// For multiple Firebase apps
void initializeMultipleApps() async {
// Default app
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Secondary app
await Firebase.initializeApp(
name: 'secondary',
options: SecondaryFirebaseOptions.currentPlatform,
);
// Access secondary app
FirebaseApp secondary = Firebase.app('secondary');
}6. How do you implement email and password authentication in Flutter using Firebase?
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Sign Up
Future<UserCredential?> signUp(String email, String password) async {
try {
return await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
throw 'The password is too weak';
} else if (e.code == 'email-already-in-use') {
throw 'An account already exists for this email';
}
rethrow;
}
}
// Sign In
Future<UserCredential?> signIn(String email, String password) async {
try {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw 'No user found for this email';
} else if (e.code == 'wrong-password') {
throw 'Wrong password';
}
rethrow;
}
}
// Sign Out
Future<void> signOut() async {
await _auth.signOut();
}
// Current User
User? get currentUser => _auth.currentUser;
}7. How do you implement Google Sign-In with Firebase Authentication in Flutter?
# pubspec.yaml
dependencies:
google_sign_in: ^6.1.6
firebase_auth: ^4.15.0import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class GoogleAuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Future<UserCredential?> signInWithGoogle() async {
try {
// Trigger Google sign-in flow
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return null; // User cancelled
// Get auth details
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Create Firebase credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Sign in to Firebase
return await _auth.signInWithCredential(credential);
} catch (e) {
print('Google Sign-In Error: $e');
rethrow;
}
}
Future<void> signOut() async {
await _googleSignIn.signOut();
await _auth.signOut();
}
}8. What is the process for implementing phone number authentication with Firebase in Flutter?
class PhoneAuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
String? _verificationId;
// Step 1: Send verification code
Future<void> verifyPhoneNumber(String phoneNumber) async {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
// Auto-verification (Android only)
verificationCompleted: (PhoneAuthCredential credential) async {
await _auth.signInWithCredential(credential);
},
// Verification failed
verificationFailed: (FirebaseAuthException e) {
throw 'Phone verification failed: ${e.message}';
},
// Code sent successfully
codeSent: (String verificationId, int? resendToken) {
_verificationId = verificationId;
},
// Timeout
codeAutoRetrievalTimeout: (String verificationId) {
_verificationId = verificationId;
},
timeout: const Duration(seconds: 60),
);
}
// Step 2: Verify the code
Future<UserCredential> verifyCode(String smsCode) async {
if (_verificationId == null) {
throw 'Verification ID is null. Call verifyPhoneNumber first.';
}
final credential = PhoneAuthProvider.credential(
verificationId: _verificationId!,
smsCode: smsCode,
);
return await _auth.signInWithCredential(credential);
}
}9. How do you implement anonymous authentication in Flutter?
class AnonymousAuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Sign in anonymously
Future<UserCredential> signInAnonymously() async {
return await _auth.signInAnonymously();
}
// Check if user is anonymous
bool get isAnonymous => _auth.currentUser?.isAnonymous ?? false;
// Link anonymous account to email/password
Future<UserCredential> linkWithEmail(String email, String password) async {
final credential = EmailAuthProvider.credential(
email: email,
password: password,
);
return await _auth.currentUser!.linkWithCredential(credential);
}
// Link anonymous account to Google
Future<UserCredential> linkWithGoogle() async {
final GoogleSignIn googleSignIn = GoogleSignIn();
final googleUser = await googleSignIn.signIn();
if (googleUser == null) throw 'Google sign-in cancelled';
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
return await _auth.currentUser!.linkWithCredential(credential);
}
}10. How do you handle password reset functionality in Firebase Authentication?
class PasswordResetService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Send password reset email
Future<void> sendPasswordResetEmail(String email) async {
try {
await _auth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw 'No user found with this email';
}
rethrow;
}
}
// Confirm password reset with code (from deep link)
Future<void> confirmPasswordReset(String code, String newPassword) async {
await _auth.confirmPasswordReset(
code: code,
newPassword: newPassword,
);
}
// Verify password reset code
Future<String> verifyPasswordResetCode(String code) async {
return await _auth.verifyPasswordResetCode(code);
}
// Update password for logged-in user
Future<void> updatePassword(String newPassword) async {
await _auth.currentUser?.updatePassword(newPassword);
}
// Re-authenticate before sensitive operations
Future<void> reauthenticate(String email, String password) async {
final credential = EmailAuthProvider.credential(
email: email,
password: password,
);
await _auth.currentUser?.reauthenticateWithCredential(credential);
}
}11. How do you verify a user's email address after registration?
class EmailVerificationService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Send verification email
Future<void> sendEmailVerification() async {
final user = _auth.currentUser;
if (user != null && !user.emailVerified) {
await user.sendEmailVerification();
}
}
// Check if email is verified
bool get isEmailVerified => _auth.currentUser?.emailVerified ?? false;
// Reload user to get updated verification status
Future<bool> checkEmailVerified() async {
await _auth.currentUser?.reload();
return _auth.currentUser?.emailVerified ?? false;
}
// Send with custom action code settings
Future<void> sendCustomVerificationEmail() async {
final actionCodeSettings = ActionCodeSettings(
url: 'https://yourapp.page.link/verify',
handleCodeInApp: true,
androidPackageName: 'com.example.app',
androidMinimumVersion: '21',
iOSBundleId: 'com.example.app',
);
await _auth.currentUser?.sendEmailVerification(actionCodeSettings);
}
}12. What is the difference between authStateChanges(), idTokenChanges(), and userChanges() in Firebase Auth?
class AuthStateService {
final FirebaseAuth _auth = FirebaseAuth.instance;
void listenToAuthChanges() {
// 1. authStateChanges() - Sign in/out events only
// Fires when: sign in, sign out
// Does NOT fire when: token refresh, profile updates
_auth.authStateChanges().listen((User? user) {
if (user == null) {
print('User signed out');
} else {
print('User signed in');
}
});
// 2. idTokenChanges() - Token-level changes
// Fires when: sign in, sign out, token refresh
// Does NOT fire when: profile updates
_auth.idTokenChanges().listen((User? user) {
if (user != null) {
print('Token refreshed');
}
});
// 3. userChanges() - All user changes
// Fires when: sign in, sign out, token refresh, profile updates
// Most comprehensive stream
_auth.userChanges().listen((User? user) {
if (user != null) {
print('User updated: ${user.displayName}');
}
});
}
}Use cases:
- authStateChanges(): Navigation between auth/non-auth screens
- idTokenChanges(): When you need fresh tokens for API calls
- userChanges(): When UI needs to reflect all user updates
13. How do you implement Apple Sign-In with Firebase in Flutter?
# pubspec.yaml
dependencies:
sign_in_with_apple: ^5.0.0
firebase_auth: ^4.15.0import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:firebase_auth/firebase_auth.dart';
class AppleAuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Generate nonce for security
String _generateNonce([int length = 32]) {
const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)]).join();
}
String _sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
Future<UserCredential> signInWithApple() async {
final rawNonce = _generateNonce();
final nonce = _sha256ofString(rawNonce);
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
final oauthCredential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
return await _auth.signInWithCredential(oauthCredential);
}
}14. How do you sign out a user from Firebase Authentication?
class SignOutService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Basic sign out
Future<void> signOut() async {
await _auth.signOut();
}
// Sign out from all providers
Future<void> signOutFromAll() async {
// Sign out from Google if used
try {
await GoogleSignIn().signOut();
} catch (_) {}
// Sign out from Facebook if used
// await FacebookAuth.instance.logOut();
// Sign out from Firebase
await _auth.signOut();
}
// Sign out and clear local data
Future<void> signOutAndClear() async {
await _auth.signOut();
// Clear local storage
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
// Clear Hive boxes if used
// await Hive.deleteFromDisk();
}
}15. How do you implement multi-factor authentication (MFA) in Flutter with Firebase?
class MFAService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Enroll phone as second factor
Future<void> enrollPhoneMFA(String phoneNumber) async {
final session = await _auth.currentUser!.multiFactor.getSession();
await _auth.verifyPhoneNumber(
multiFactorSession: session,
phoneNumber: phoneNumber,
verificationCompleted: (_) {},
verificationFailed: (e) => throw e,
codeSent: (verificationId, _) async {
// Get SMS code from user
String smsCode = await getSmsCodeFromUser();
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
await _auth.currentUser!.multiFactor.enroll(
PhoneMultiFactorGenerator.getAssertion(credential),
);
},
codeAutoRetrievalTimeout: (_) {},
);
}
// Handle MFA during sign-in
Future<UserCredential> signInWithMFA(String email, String password) async {
try {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthMultiFactorException catch (e) {
// MFA required
final resolver = e.resolver;
final hint = resolver.hints.first as PhoneMultiFactorInfo;
await _auth.verifyPhoneNumber(
multiFactorSession: resolver.session,
multiFactorInfo: hint,
verificationCompleted: (_) {},
verificationFailed: (e) => throw e,
codeSent: (verificationId, _) async {
String smsCode = await getSmsCodeFromUser();
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
await resolver.resolveSignIn(
PhoneMultiFactorGenerator.getAssertion(credential),
);
},
codeAutoRetrievalTimeout: (_) {},
);
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
}
}
Future<String> getSmsCodeFromUser() async {
// Show dialog to get SMS code
throw UnimplementedError();
}
}16. How do you perform basic CRUD operations in Cloud Firestore?
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// CREATE
Future<DocumentReference> addUser(Map<String, dynamic> userData) async {
return await _db.collection('users').add(userData);
}
// CREATE with specific ID
Future<void> setUser(String id, Map<String, dynamic> userData) async {
await _db.collection('users').doc(id).set(userData);
}
// READ single document
Future<DocumentSnapshot> getUser(String id) async {
return await _db.collection('users').doc(id).get();
}
// READ all documents
Future<QuerySnapshot> getAllUsers() async {
return await _db.collection('users').get();
}
// UPDATE
Future<void> updateUser(String id, Map<String, dynamic> data) async {
await _db.collection('users').doc(id).update(data);
}
// UPDATE specific fields
Future<void> updateUserFields(String id) async {
await _db.collection('users').doc(id).update({
'name': 'New Name',
'updatedAt': FieldValue.serverTimestamp(),
'score': FieldValue.increment(10),
});
}
// DELETE
Future<void> deleteUser(String id) async {
await _db.collection('users').doc(id).delete();
}
// DELETE field
Future<void> deleteField(String id, String fieldName) async {
await _db.collection('users').doc(id).update({
fieldName: FieldValue.delete(),
});
}
}17. How do you query documents in Firestore with where clauses and ordering?
class FirestoreQueryService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final CollectionReference users = FirebaseFirestore.instance.collection('users');
// Simple where clause
Future<QuerySnapshot> getActiveUsers() {
return users.where('isActive', isEqualTo: true).get();
}
// Multiple conditions
Future<QuerySnapshot> getFilteredUsers() {
return users
.where('age', isGreaterThanOrEqualTo: 18)
.where('age', isLessThan: 65)
.get();
}
// Where with ordering
Future<QuerySnapshot> getRecentActiveUsers() {
return users
.where('isActive', isEqualTo: true)
.orderBy('createdAt', descending: true)
.limit(10)
.get();
}
// Array contains
Future<QuerySnapshot> getUsersByTag(String tag) {
return users.where('tags', arrayContains: tag).get();
}
// Array contains any
Future<QuerySnapshot> getUsersByTags(List<String> tags) {
return users.where('tags', arrayContainsAny: tags).get();
}
// Where in
Future<QuerySnapshot> getUsersByIds(List<String> ids) {
return users.where(FieldPath.documentId, whereIn: ids).get();
}
// Compound query (requires composite index)
Future<QuerySnapshot> complexQuery() {
return users
.where('status', isEqualTo: 'active')
.where('role', isEqualTo: 'admin')
.orderBy('createdAt', descending: true)
.get();
}
}18. What is the difference between get() and snapshots() methods in Firestore?
class FirestoreReadService {
final CollectionReference users =
FirebaseFirestore.instance.collection('users');
// get() - One-time fetch
// Returns Future<QuerySnapshot>
// Use when you need data once
Future<void> fetchOnce() async {
QuerySnapshot snapshot = await users.get();
for (var doc in snapshot.docs) {
print(doc.data());
}
}
// snapshots() - Real-time stream
// Returns Stream<QuerySnapshot>
// Use when you need live updates
Stream<QuerySnapshot> watchUsers() {
return users.snapshots();
}
// Using in StreamBuilder
Widget buildUserList() {
return StreamBuilder<QuerySnapshot>(
stream: users.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return ListView(
children: snapshot.data!.docs.map((doc) {
return ListTile(title: Text(doc['name']));
}).toList(),
);
},
);
}
// Document-level streams
Stream<DocumentSnapshot> watchSingleUser(String id) {
return users.doc(id).snapshots();
}
}Key differences:
get(): Single fetch, returnsFuturesnapshots(): Continuous updates, returnsStream
19. How do you implement real-time listeners in Firestore?
class RealtimeListenerService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
StreamSubscription? _subscription;
// Listen to collection
void listenToUsers(Function(List<Map<String, dynamic>>) onData) {
_subscription = _db.collection('users').snapshots().listen(
(snapshot) {
List<Map<String, dynamic>> users = snapshot.docs
.map((doc) => {'id': doc.id, ...doc.data()})
.toList();
onData(users);
},
onError: (error) => print('Listen error: $error'),
);
}
// Listen to document
void listenToUser(String id, Function(Map<String, dynamic>?) onData) {
_subscription = _db.collection('users').doc(id).snapshots().listen(
(snapshot) {
if (snapshot.exists) {
onData({'id': snapshot.id, ...snapshot.data()!});
} else {
onData(null);
}
},
);
}
// Listen with docChanges for efficiency
void listenToChanges() {
_db.collection('users').snapshots().listen((snapshot) {
for (var change in snapshot.docChanges) {
switch (change.type) {
case DocumentChangeType.added:
print('Added: ${change.doc.data()}');
break;
case DocumentChangeType.modified:
print('Modified: ${change.doc.data()}');
break;
case DocumentChangeType.removed:
print('Removed: ${change.doc.id}');
break;
}
}
});
}
// Cancel subscription
void dispose() {
_subscription?.cancel();
}
}20. How do you perform batch writes in Firestore?
class BatchWriteService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Batch write - up to 500 operations
Future<void> batchWrite() async {
WriteBatch batch = _db.batch();
// Create
batch.set(_db.collection('users').doc('user1'), {
'name': 'John',
'createdAt': FieldValue.serverTimestamp(),
});
// Update
batch.update(_db.collection('users').doc('user2'), {
'score': FieldValue.increment(10),
});
// Delete
batch.delete(_db.collection('users').doc('user3'));
// Commit all operations atomically
await batch.commit();
}
// Batch with multiple documents
Future<void> batchCreateUsers(List<Map<String, dynamic>> users) async {
WriteBatch batch = _db.batch();
for (var userData in users) {
DocumentReference ref = _db.collection('users').doc();
batch.set(ref, userData);
}
await batch.commit();
}
// Chunked batch for more than 500 operations
Future<void> largeBatchWrite(List<Map<String, dynamic>> items) async {
const int batchSize = 500;
for (int i = 0; i < items.length; i += batchSize) {
WriteBatch batch = _db.batch();
int end = (i + batchSize < items.length) ? i + batchSize : items.length;
for (int j = i; j < end; j++) {
batch.set(_db.collection('items').doc(), items[j]);
}
await batch.commit();
}
}
}21. What are Firestore transactions and when should you use them?
Transactions ensure atomic read-then-write operations.
class TransactionService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Transfer points between users
Future<void> transferPoints(
String fromUserId,
String toUserId,
int amount,
) async {
await _db.runTransaction((transaction) async {
// Read documents
DocumentSnapshot fromDoc =
await transaction.get(_db.collection('users').doc(fromUserId));
DocumentSnapshot toDoc =
await transaction.get(_db.collection('users').doc(toUserId));
int fromPoints = fromDoc.get('points') as int;
if (fromPoints < amount) {
throw Exception('Insufficient points');
}
// Write updates
transaction.update(fromDoc.reference, {
'points': fromPoints - amount,
});
transaction.update(toDoc.reference, {
'points': FieldValue.increment(amount),
});
});
}
// Increment counter safely
Future<int> incrementCounter(String docId) async {
DocumentReference ref = _db.collection('counters').doc(docId);
return await _db.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(ref);
int currentValue = snapshot.exists ? snapshot.get('value') as int : 0;
int newValue = currentValue + 1;
transaction.set(ref, {'value': newValue});
return newValue;
});
}
}Use transactions when:
- Reading and writing to same document
- Need to ensure data consistency
- Operations depend on current values
22. How do you implement pagination in Firestore queries?
class PaginationService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
DocumentSnapshot? _lastDocument;
final int _pageSize = 20;
// Initial load
Future<List<DocumentSnapshot>> getFirstPage() async {
Query query = _db
.collection('posts')
.orderBy('createdAt', descending: true)
.limit(_pageSize);
QuerySnapshot snapshot = await query.get();
if (snapshot.docs.isNotEmpty) {
_lastDocument = snapshot.docs.last;
}
return snapshot.docs;
}
// Load next page
Future<List<DocumentSnapshot>> getNextPage() async {
if (_lastDocument == null) return [];
Query query = _db
.collection('posts')
.orderBy('createdAt', descending: true)
.startAfterDocument(_lastDocument!)
.limit(_pageSize);
QuerySnapshot snapshot = await query.get();
if (snapshot.docs.isNotEmpty) {
_lastDocument = snapshot.docs.last;
}
return snapshot.docs;
}
// Reset pagination
void reset() {
_lastDocument = null;
}
// Check if more data available
bool get hasMore => _lastDocument != null;
}23. How do you work with subcollections in Firestore?
class SubcollectionService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Add document to subcollection
Future<void> addComment(String postId, Map<String, dynamic> comment) async {
await _db
.collection('posts')
.doc(postId)
.collection('comments')
.add(comment);
}
// Get subcollection documents
Future<QuerySnapshot> getComments(String postId) {
return _db
.collection('posts')
.doc(postId)
.collection('comments')
.orderBy('createdAt', descending: true)
.get();
}
// Stream subcollection
Stream<QuerySnapshot> watchComments(String postId) {
return _db
.collection('posts')
.doc(postId)
.collection('comments')
.snapshots();
}
// Collection group query (query across all subcollections)
Future<QuerySnapshot> getAllCommentsByUser(String userId) {
return _db
.collectionGroup('comments')
.where('userId', isEqualTo: userId)
.get();
}
// Delete subcollection (must delete docs individually)
Future<void> deleteComments(String postId) async {
WriteBatch batch = _db.batch();
QuerySnapshot comments = await _db
.collection('posts')
.doc(postId)
.collection('comments')
.get();
for (var doc in comments.docs) {
batch.delete(doc.reference);
}
await batch.commit();
}
}24. How do you update array fields in Firestore documents?
class ArrayUpdateService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Add to array (avoid duplicates)
Future<void> addTag(String docId, String tag) async {
await _db.collection('posts').doc(docId).update({
'tags': FieldValue.arrayUnion([tag]),
});
}
// Add multiple items
Future<void> addTags(String docId, List<String> tags) async {
await _db.collection('posts').doc(docId).update({
'tags': FieldValue.arrayUnion(tags),
});
}
// Remove from array
Future<void> removeTag(String docId, String tag) async {
await _db.collection('posts').doc(docId).update({
'tags': FieldValue.arrayRemove([tag]),
});
}
// Replace entire array
Future<void> setTags(String docId, List<String> tags) async {
await _db.collection('posts').doc(docId).update({
'tags': tags,
});
}
// For complex objects in arrays (need to replace whole array)
Future<void> updateArrayItem(String docId, int index, Map<String, dynamic> newItem) async {
DocumentSnapshot doc = await _db.collection('posts').doc(docId).get();
List items = List.from(doc.get('items'));
items[index] = newItem;
await _db.collection('posts').doc(docId).update({'items': items});
}
}25. How do you handle Firestore timestamps correctly?
class TimestampService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Write with server timestamp
Future<void> createPost(Map<String, dynamic> data) async {
await _db.collection('posts').add({
...data,
'createdAt': FieldValue.serverTimestamp(),
'updatedAt': FieldValue.serverTimestamp(),
});
}
// Read timestamp
DateTime? getCreatedAt(DocumentSnapshot doc) {
Timestamp? timestamp = doc.get('createdAt') as Timestamp?;
return timestamp?.toDate();
}
// Query by timestamp
Future<QuerySnapshot> getPostsAfter(DateTime date) {
return _db
.collection('posts')
.where('createdAt', isGreaterThan: Timestamp.fromDate(date))
.get();
}
// Convert for display
String formatTimestamp(Timestamp timestamp) {
DateTime date = timestamp.toDate();
return '${date.day}/${date.month}/${date.year}';
}
// Handle pending writes (local cache)
void handlePendingTimestamp(DocumentSnapshot doc) {
if (doc.metadata.hasPendingWrites) {
// Document has local changes not yet synced
// createdAt might be null
print('Pending write - timestamp may be null');
} else {
Timestamp timestamp = doc.get('createdAt');
print('Confirmed: ${timestamp.toDate()}');
}
}
}26. What are composite indexes in Firestore and when do you need them?
Composite indexes are required for queries that combine:
- Multiple
whereclauses on different fields whereclause withorderByon different fields- Multiple range comparisons
// This query requires a composite index
// Firestore will throw an error with a link to create the index
Query query = _db.collection('products')
.where('category', isEqualTo: 'electronics')
.where('price', isLessThan: 100)
.orderBy('price');
// Another example requiring composite index
Query query2 = _db.collection('users')
.where('role', isEqualTo: 'admin')
.where('status', isEqualTo: 'active')
.orderBy('createdAt', descending: true);Creating indexes:
- Automatic: Run the query, click the error link
- Manual: Firebase Console → Firestore → Indexes
- firestore.indexes.json:
{
"indexes": [
{
"collectionGroup": "products",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "order": "ASCENDING" },
{ "fieldPath": "price", "order": "ASCENDING" }
]
}
]
}27. How do you handle errors in Firestore operations?
class FirestoreErrorHandler {
final FirebaseFirestore _db = FirebaseFirestore.instance;
Future<void> safeOperation() async {
try {
await _db.collection('users').doc('user1').get();
} on FirebaseException catch (e) {
switch (e.code) {
case 'permission-denied':
throw 'You do not have permission to access this data';
case 'unavailable':
throw 'Service temporarily unavailable. Please try again.';
case 'not-found':
throw 'Document not found';
case 'already-exists':
throw 'Document already exists';
case 'resource-exhausted':
throw 'Quota exceeded. Please try again later.';
case 'failed-precondition':
throw 'Operation failed. Index may be required.';
case 'aborted':
throw 'Operation was aborted. Please retry.';
default:
throw 'An error occurred: ${e.message}';
}
} catch (e) {
throw 'Unexpected error: $e';
}
}
// With retry logic
Future<T> withRetry<T>(Future<T> Function() operation, {int maxAttempts = 3}) async {
int attempts = 0;
while (attempts < maxAttempts) {
try {
return await operation();
} on FirebaseException catch (e) {
attempts++;
if (e.code == 'unavailable' && attempts < maxAttempts) {
await Future.delayed(Duration(seconds: attempts * 2));
continue;
}
rethrow;
}
}
throw 'Max retry attempts reached';
}
}28. What is the difference between Cloud Firestore and Firebase Realtime Database?
| Feature | Cloud Firestore | Realtime Database |
|---|---|---|
| Data Model | Documents & Collections | JSON tree |
| Queries | Complex, indexed | Simple, limited |
| Offline | Full support | Limited |
| Scaling | Auto-scales | Manual sharding |
| Pricing | Per operation | Per bandwidth/storage |
| Real-time | Per-document | Entire path |
// Firestore - Document-based
FirebaseFirestore.instance
.collection('users')
.doc('user1')
.set({'name': 'John', 'age': 30});
// Realtime Database - JSON tree
FirebaseDatabase.instance
.ref('users/user1')
.set({'name': 'John', 'age': 30});Choose Firestore for: Complex queries, better offline, larger scale Choose Realtime Database for: Simpler data, lower latency, presence systems
29. How do you perform CRUD operations in Firebase Realtime Database?
import 'package:firebase_database/firebase_database.dart';
class RealtimeDBService {
final DatabaseReference _db = FirebaseDatabase.instance.ref();
// CREATE
Future<void> createUser(String userId, Map<String, dynamic> data) async {
await _db.child('users/$userId').set(data);
}
// CREATE with auto-generated key
Future<String> addMessage(Map<String, dynamic> message) async {
DatabaseReference newRef = _db.child('messages').push();
await newRef.set(message);
return newRef.key!;
}
// READ once
Future<DataSnapshot> getUser(String userId) async {
return await _db.child('users/$userId').get();
}
// UPDATE
Future<void> updateUser(String userId, Map<String, dynamic> updates) async {
await _db.child('users/$userId').update(updates);
}
// DELETE
Future<void> deleteUser(String userId) async {
await _db.child('users/$userId').remove();
}
// Multi-path update
Future<void> multiUpdate() async {
await _db.update({
'users/user1/score': 100,
'users/user2/score': 200,
'leaderboard/user1': 100,
});
}
}30. How do you implement real-time listeners in Firebase Realtime Database?
class RealtimeListeners {
final DatabaseReference _db = FirebaseDatabase.instance.ref();
StreamSubscription? _subscription;
// Listen to value changes
void listenToUser(String userId, Function(Map<String, dynamic>?) callback) {
_subscription = _db.child('users/$userId').onValue.listen((event) {
if (event.snapshot.exists) {
callback(Map<String, dynamic>.from(event.snapshot.value as Map));
} else {
callback(null);
}
});
}
// Listen to child events
void listenToMessages() {
// New children
_db.child('messages').onChildAdded.listen((event) {
print('New message: ${event.snapshot.value}');
});
// Changed children
_db.child('messages').onChildChanged.listen((event) {
print('Updated message: ${event.snapshot.value}');
});
// Removed children
_db.child('messages').onChildRemoved.listen((event) {
print('Deleted message: ${event.snapshot.key}');
});
}
// Query with listener
void listenToRecentMessages(int limit) {
_db
.child('messages')
.orderByChild('timestamp')
.limitToLast(limit)
.onValue
.listen((event) {
// Handle data
});
}
void dispose() {
_subscription?.cancel();
}
}31. How does offline persistence work in Firebase Realtime Database?
class OfflinePersistence {
void configure() {
// Enable persistence (enabled by default on mobile)
FirebaseDatabase.instance.setPersistenceEnabled(true);
// Set cache size (default 10MB)
FirebaseDatabase.instance.setPersistenceCacheSizeBytes(10000000);
}
// Keep data synced even when not listening
void keepSynced() {
FirebaseDatabase.instance
.ref('important/data')
.keepSynced(true);
}
// Check connection state
void monitorConnection() {
FirebaseDatabase.instance
.ref('.info/connected')
.onValue
.listen((event) {
bool connected = event.snapshot.value as bool? ?? false;
print(connected ? 'Connected' : 'Disconnected');
});
}
// Handle offline writes
void offlineWrite() {
// Writes are queued locally and synced when online
FirebaseDatabase.instance
.ref('messages')
.push()
.set({
'text': 'Hello',
'timestamp': ServerValue.timestamp,
});
}
// On disconnect triggers
void setupPresence(String odId) {
DatabaseReference presenceRef =
FirebaseDatabase.instance.ref('presence/$userId');
// Set online
presenceRef.set(true);
// Clear on disconnect
presenceRef.onDisconnect().set(false);
}
}32. How do you upload files to Firebase Storage from Flutter?
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';
class StorageUploadService {
final FirebaseStorage _storage = FirebaseStorage.instance;
// Upload file
Future<String> uploadFile(File file, String path) async {
Reference ref = _storage.ref().child(path);
UploadTask uploadTask = ref.putFile(file);
TaskSnapshot snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
}
// Upload with metadata
Future<String> uploadWithMetadata(File file, String path) async {
Reference ref = _storage.ref().child(path);
SettableMetadata metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'uploadedBy': 'user123'},
);
UploadTask uploadTask = ref.putFile(file, metadata);
TaskSnapshot snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
}
// Upload from bytes
Future<String> uploadBytes(Uint8List data, String path) async {
Reference ref = _storage.ref().child(path);
UploadTask uploadTask = ref.putData(data);
TaskSnapshot snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
}
// Generate unique path
String generatePath(String userId, String filename) {
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
String extension = filename.split('.').last;
return 'uploads/$userId/${timestamp}.$extension';
}
}33. How do you download files from Firebase Storage?
class StorageDownloadService {
final FirebaseStorage _storage = FirebaseStorage.instance;
// Get download URL
Future<String> getDownloadUrl(String path) async {
return await _storage.ref(path).getDownloadURL();
}
// Download to file
Future<File> downloadToFile(String path, String localPath) async {
File file = File(localPath);
await _storage.ref(path).writeToFile(file);
return file;
}
// Download as bytes
Future<Uint8List?> downloadAsBytes(String path) async {
return await _storage.ref(path).getData();
}
// Get metadata
Future<FullMetadata> getMetadata(String path) async {
return await _storage.ref(path).getMetadata();
}
// List files in directory
Future<ListResult> listFiles(String path) async {
return await _storage.ref(path).listAll();
}
// Delete file
Future<void> deleteFile(String path) async {
await _storage.ref(path).delete();
}
}34. How do you track upload progress in Firebase Storage?
class UploadProgressService {
final FirebaseStorage _storage = FirebaseStorage.instance;
// Upload with progress tracking
void uploadWithProgress(
File file,
String path,
Function(double) onProgress,
Function(String) onComplete,
Function(String) onError,
) {
Reference ref = _storage.ref().child(path);
UploadTask uploadTask = ref.putFile(file);
// Listen to state changes
uploadTask.snapshotEvents.listen(
(TaskSnapshot snapshot) {
double progress =
snapshot.bytesTransferred / snapshot.totalBytes;
onProgress(progress);
switch (snapshot.state) {
case TaskState.running:
print('Upload running: ${(progress * 100).toStringAsFixed(2)}%');
break;
case TaskState.paused:
print('Upload paused');
break;
case TaskState.success:
print('Upload complete');
break;
case TaskState.canceled:
print('Upload canceled');
break;
case TaskState.error:
print('Upload error');
break;
}
},
onError: (e) => onError(e.toString()),
);
// Get download URL on completion
uploadTask.then((snapshot) async {
String url = await snapshot.ref.getDownloadURL();
onComplete(url);
});
}
// Pause/Resume/Cancel
void controlUpload(UploadTask task) {
task.pause();
task.resume();
task.cancel();
}
}35. How do you implement image compression before uploading to Firebase Storage?
# pubspec.yaml
dependencies:
flutter_image_compress: ^2.1.0import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'dart:io';
class ImageCompressionService {
// Compress image file
Future<File?> compressImage(File file, {int quality = 80}) async {
final String targetPath = file.path.replaceAll(
RegExp(r'\.(jpg|jpeg|png)$'),
'_compressed.jpg',
);
XFile? result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: quality,
minWidth: 1024,
minHeight: 1024,
);
return result != null ? File(result.path) : null;
}
// Compress and upload
Future<String?> compressAndUpload(
File file,
String storagePath,
{int quality = 80}
) async {
// Compress
File? compressed = await compressImage(file, quality: quality);
if (compressed == null) return null;
// Upload
Reference ref = FirebaseStorage.instance.ref().child(storagePath);
UploadTask task = ref.putFile(compressed);
TaskSnapshot snapshot = await task;
// Cleanup compressed file
await compressed.delete();
return await snapshot.ref.getDownloadURL();
}
// Generate thumbnail
Future<Uint8List?> generateThumbnail(File file) async {
return await FlutterImageCompress.compressWithFile(
file.absolute.path,
quality: 60,
minWidth: 200,
minHeight: 200,
);
}
}36. How do you call Cloud Functions from Flutter?
import 'package:cloud_functions/cloud_functions.dart';
class CloudFunctionsService {
final FirebaseFunctions _functions = FirebaseFunctions.instance;
// Call callable function
Future<dynamic> callFunction(String name, Map<String, dynamic> data) async {
HttpsCallable callable = _functions.httpsCallable(name);
HttpsCallableResult result = await callable.call(data);
return result.data;
}
// Example: Process payment
Future<Map<String, dynamic>> processPayment(double amount) async {
HttpsCallable callable = _functions.httpsCallable('processPayment');
HttpsCallableResult result = await callable.call({
'amount': amount,
'currency': 'USD',
});
return Map<String, dynamic>.from(result.data);
}
// With timeout
Future<dynamic> callWithTimeout(String name, Map<String, dynamic> data) async {
HttpsCallable callable = _functions.httpsCallable(
name,
options: HttpsCallableOptions(timeout: Duration(seconds: 30)),
);
return (await callable.call(data)).data;
}
// Use different region
Future<dynamic> callRegionalFunction(String name) async {
FirebaseFunctions regionalFunctions =
FirebaseFunctions.instanceFor(region: 'europe-west1');
HttpsCallable callable = regionalFunctions.httpsCallable(name);
return (await callable.call()).data;
}
}37. What are the different types of Cloud Functions triggers?
// 1. HTTPS Callable (called from client)
exports.processOrder = functions.https.onCall((data, context) => {
// Verify authentication
if (!context.auth) throw new functions.https.HttpsError('unauthenticated');
return { success: true, orderId: 'order123' };
});
// 2. HTTPS Request (REST API)
exports.api = functions.https.onRequest((req, res) => {
res.json({ message: 'Hello' });
});
// 3. Firestore Triggers
exports.onUserCreate = functions.firestore
.document('users/{userId}')
.onCreate((snap, context) => {
const userData = snap.data();
// Send welcome email
});
exports.onUserUpdate = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const before = change.before.data();
const after = change.after.data();
});
// 4. Authentication Triggers
exports.onUserSignUp = functions.auth.user().onCreate((user) => {
// Create user profile
});
// 5. Storage Triggers
exports.onFileUpload = functions.storage
.object()
.onFinalize((object) => {
// Generate thumbnail
});
// 6. Scheduled Functions
exports.dailyCleanup = functions.pubsub
.schedule('every 24 hours')
.onRun((context) => {
// Cleanup old data
});38. How do you handle errors when calling Cloud Functions from Flutter?
class FunctionErrorHandler {
final FirebaseFunctions _functions = FirebaseFunctions.instance;
Future<dynamic> safeCall(String name, Map<String, dynamic> data) async {
try {
HttpsCallable callable = _functions.httpsCallable(name);
HttpsCallableResult result = await callable.call(data);
return result.data;
} on FirebaseFunctionsException catch (e) {
// Handle specific error codes
switch (e.code) {
case 'unauthenticated':
throw 'Please sign in to continue';
case 'permission-denied':
throw 'You do not have permission for this action';
case 'not-found':
throw 'The requested resource was not found';
case 'already-exists':
throw 'This resource already exists';
case 'invalid-argument':
throw 'Invalid data provided: ${e.message}';
case 'deadline-exceeded':
throw 'Request timeout. Please try again.';
case 'resource-exhausted':
throw 'Too many requests. Please wait.';
case 'internal':
throw 'Server error. Please try again later.';
default:
throw e.message ?? 'An error occurred';
}
} catch (e) {
throw 'Unexpected error: $e';
}
}
// With retry logic
Future<dynamic> callWithRetry(
String name,
Map<String, dynamic> data,
{int maxRetries = 3}
) async {
for (int i = 0; i < maxRetries; i++) {
try {
return await safeCall(name, data);
} on FirebaseFunctionsException catch (e) {
if (e.code == 'unavailable' && i < maxRetries - 1) {
await Future.delayed(Duration(seconds: (i + 1) * 2));
continue;
}
rethrow;
}
}
}
}39. How do you set up Firebase Cloud Messaging (FCM) in a Flutter app?
# pubspec.yaml
dependencies:
firebase_messaging: ^14.7.0import 'package:firebase_messaging/firebase_messaging.dart';
class FCMService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
Future<void> initialize() async {
// Request permission (iOS)
NotificationSettings settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: false,
);
print('Permission status: ${settings.authorizationStatus}');
// Get FCM token
String? token = await _messaging.getToken();
print('FCM Token: $token');
// Listen for token refresh
_messaging.onTokenRefresh.listen((newToken) {
print('Token refreshed: $newToken');
// Send to server
});
}
// Get APNS token (iOS only)
Future<String?> getAPNSToken() async {
return await _messaging.getAPNSToken();
}
}40. How do you handle foreground notifications in Flutter?
class ForegroundNotificationHandler {
void setupForegroundHandler() {
// Handle messages when app is in foreground
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Received message in foreground');
print('Title: ${message.notification?.title}');
print('Body: ${message.notification?.body}');
print('Data: ${message.data}');
// Show local notification
_showLocalNotification(message);
});
}
void _showLocalNotification(RemoteMessage message) {
// Using flutter_local_notifications
FlutterLocalNotificationsPlugin notifications =
FlutterLocalNotificationsPlugin();
const AndroidNotificationDetails androidDetails =
AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
);
const NotificationDetails details = NotificationDetails(
android: androidDetails,
iOS: DarwinNotificationDetails(),
);
notifications.show(
message.hashCode,
message.notification?.title,
message.notification?.body,
details,
payload: jsonEncode(message.data),
);
}
}41. How do you handle background and terminated state notifications?
// Top-level function (outside any class)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Background message: ${message.messageId}');
// Handle message
}
class BackgroundNotificationHandler {
void initialize() {
// Register background handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Handle notification tap when app was terminated
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
_handleNotificationTap(message);
}
});
// Handle notification tap when app was in background
FirebaseMessaging.onMessageOpenedApp.listen((message) {
_handleNotificationTap(message);
});
}
void _handleNotificationTap(RemoteMessage message) {
print('Notification tapped');
print('Data: ${message.data}');
// Navigate based on data
if (message.data['type'] == 'chat') {
// Navigate to chat screen
} else if (message.data['type'] == 'order') {
// Navigate to order details
}
}
}42. How do you subscribe to and unsubscribe from FCM topics?
class TopicService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
// Subscribe to topic
Future<void> subscribeToTopic(String topic) async {
await _messaging.subscribeToTopic(topic);
print('Subscribed to $topic');
}
// Unsubscribe from topic
Future<void> unsubscribeFromTopic(String topic) async {
await _messaging.unsubscribeFromTopic(topic);
print('Unsubscribed from $topic');
}
// Example usage
Future<void> setupUserTopics(String userId, List<String> interests) async {
// Subscribe to user-specific topic
await subscribeToTopic('user_$userId');
// Subscribe to interest topics
for (String interest in interests) {
await subscribeToTopic('interest_$interest');
}
// Subscribe to general topics
await subscribeToTopic('announcements');
}
// Cleanup on logout
Future<void> unsubscribeAll(String userId, List<String> interests) async {
await unsubscribeFromTopic('user_$userId');
for (String interest in interests) {
await unsubscribeFromTopic('interest_$interest');
}
}
}43. How do you log custom events in Firebase Analytics?
import 'package:firebase_analytics/firebase_analytics.dart';
class AnalyticsService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
// Log custom event
Future<void> logEvent(String name, Map<String, dynamic>? parameters) async {
await _analytics.logEvent(
name: name,
parameters: parameters,
);
}
// Log purchase
Future<void> logPurchase(double value, String currency, List<AnalyticsEventItem> items) async {
await _analytics.logPurchase(
value: value,
currency: currency,
items: items,
);
}
// Log screen view
Future<void> logScreenView(String screenName) async {
await _analytics.logScreenView(
screenName: screenName,
screenClass: screenName,
);
}
// Example: E-commerce events
Future<void> logAddToCart(String itemId, String itemName, double price) async {
await _analytics.logAddToCart(
items: [
AnalyticsEventItem(
itemId: itemId,
itemName: itemName,
price: price,
quantity: 1,
),
],
value: price,
currency: 'USD',
);
}
// Example: Custom business event
Future<void> logSubscription(String plan, double price) async {
await logEvent('subscription_started', {
'plan_name': plan,
'price': price,
'timestamp': DateTime.now().toIso8601String(),
});
}
}44. How do you set user properties in Firebase Analytics?
class UserPropertiesService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
// Set user ID
Future<void> setUserId(String? userId) async {
await _analytics.setUserId(id: userId);
}
// Set user property
Future<void> setUserProperty(String name, String? value) async {
await _analytics.setUserProperty(
name: name,
value: value,
);
}
// Example: Set user profile properties
Future<void> setUserProfile({
required String userId,
required String accountType,
required String subscriptionLevel,
String? preferredLanguage,
}) async {
await setUserId(userId);
await setUserProperty('account_type', accountType);
await setUserProperty('subscription_level', subscriptionLevel);
await setUserProperty('preferred_language', preferredLanguage);
}
// Reset on logout
Future<void> clearUserData() async {
await setUserId(null);
await setUserProperty('account_type', null);
await setUserProperty('subscription_level', null);
}
// Enable/disable analytics collection
Future<void> setAnalyticsEnabled(bool enabled) async {
await _analytics.setAnalyticsCollectionEnabled(enabled);
}
}45. How do you set up Firebase Crashlytics in Flutter?
# pubspec.yaml
dependencies:
firebase_crashlytics: ^3.4.0import 'package:firebase_crashlytics/firebase_crashlytics.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Pass all uncaught Flutter errors to Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
// Pass all uncaught asynchronous errors to Crashlytics
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(MyApp());
}
class CrashlyticsService {
final FirebaseCrashlytics _crashlytics = FirebaseCrashlytics.instance;
// Enable/disable collection
Future<void> setCrashlyticsEnabled(bool enabled) async {
await _crashlytics.setCrashlyticsCollectionEnabled(enabled);
}
// Set user identifier
Future<void> setUser(String userId) async {
await _crashlytics.setUserIdentifier(userId);
}
// Force crash for testing
void forceCrash() {
_crashlytics.crash();
}
}46. How do you log custom errors and non-fatal exceptions to Crashlytics?
class CrashlyticsErrorLogging {
final FirebaseCrashlytics _crashlytics = FirebaseCrashlytics.instance;
// Log non-fatal error
Future<void> logError(dynamic exception, StackTrace? stack, {String? reason}) async {
await _crashlytics.recordError(
exception,
stack,
reason: reason,
fatal: false,
);
}
// Log fatal error
Future<void> logFatalError(dynamic exception, StackTrace? stack) async {
await _crashlytics.recordError(
exception,
stack,
fatal: true,
);
}
// Add custom log message
Future<void> log(String message) async {
await _crashlytics.log(message);
}
// Set custom key-value pairs
Future<void> setCustomKey(String key, dynamic value) async {
await _crashlytics.setCustomKey(key, value);
}
// Example usage in try-catch
Future<void> performOperation() async {
try {
// Some operation
} catch (e, stack) {
await log('Operation failed');
await setCustomKey('last_action', 'performOperation');
await logError(e, stack, reason: 'Operation failed');
}
}
// Wrap async operations
Future<T?> safeAsync<T>(Future<T> Function() operation) async {
try {
return await operation();
} catch (e, stack) {
await logError(e, stack);
return null;
}
}
}47. How do you implement Firebase Remote Config in Flutter?
import 'package:firebase_remote_config/firebase_remote_config.dart';
class RemoteConfigService {
final FirebaseRemoteConfig _remoteConfig = FirebaseRemoteConfig.instance;
Future<void> initialize() async {
// Set defaults
await _remoteConfig.setDefaults({
'welcome_message': 'Welcome to our app!',
'feature_enabled': false,
'max_items': 10,
'api_url': 'https://api.example.com',
});
// Set fetch settings
await _remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(minutes: 1),
minimumFetchInterval: const Duration(hours: 1),
));
// Fetch and activate
await fetchAndActivate();
}
Future<bool> fetchAndActivate() async {
try {
await _remoteConfig.fetchAndActivate();
return true;
} catch (e) {
print('Remote config fetch failed: $e');
return false;
}
}
// Get values
String getString(String key) => _remoteConfig.getString(key);
bool getBool(String key) => _remoteConfig.getBool(key);
int getInt(String key) => _remoteConfig.getInt(key);
double getDouble(String key) => _remoteConfig.getDouble(key);
// Listen to real-time updates
void listenToUpdates() {
_remoteConfig.onConfigUpdated.listen((event) async {
await _remoteConfig.activate();
print('Config updated: ${event.updatedKeys}');
});
}
}48. How do you use Remote Config for feature flags?
class FeatureFlagService {
final FirebaseRemoteConfig _config = FirebaseRemoteConfig.instance;
// Check if feature is enabled
bool isFeatureEnabled(String featureName) {
return _config.getBool('feature_$featureName');
}
// Get feature variant (A/B testing)
String getFeatureVariant(String featureName) {
return _config.getString('variant_$featureName');
}
// Example: Conditional UI
Widget buildFeature() {
if (isFeatureEnabled('new_checkout')) {
return NewCheckoutWidget();
} else {
return OldCheckoutWidget();
}
}
// Example: Gradual rollout
bool shouldShowFeature(String userId) {
int rolloutPercentage = _config.getInt('new_feature_rollout');
int userHash = userId.hashCode.abs() % 100;
return userHash < rolloutPercentage;
}
// Example: Kill switch
bool isMaintenanceMode() {
return _config.getBool('maintenance_mode');
}
// Example: Dynamic configuration
Map<String, dynamic> getAppConfig() {
return {
'maxUploadSize': _config.getInt('max_upload_size_mb'),
'apiTimeout': _config.getInt('api_timeout_seconds'),
'showAds': _config.getBool('show_ads'),
'minAppVersion': _config.getString('min_app_version'),
};
}
}49. How do you write security rules for Cloud Firestore?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
// User profiles
match /users/{userId} {
// Anyone can read public profiles
allow read: if true;
// Only owner can write their profile
allow write: if isOwner(userId);
// Private data subcollection
match /private/{document} {
allow read, write: if isOwner(userId);
}
}
// Posts
match /posts/{postId} {
// Anyone can read published posts
allow read: if resource.data.status == 'published' || isOwner(resource.data.authorId);
// Authenticated users can create
allow create: if isAuthenticated()
&& request.resource.data.authorId == request.auth.uid
&& request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 100;
// Only author can update/delete
allow update, delete: if isOwner(resource.data.authorId);
// Comments subcollection
match /comments/{commentId} {
allow read: if true;
allow create: if isAuthenticated();
allow update, delete: if isOwner(resource.data.authorId) || isAdmin();
}
}
// Admin-only collection
match /admin/{document=**} {
allow read, write: if isAdmin();
}
}
}50. What are the best practices for structuring Firestore data and optimizing costs?
Data Structure Best Practices:
// 1. Denormalize for read efficiency
// Instead of separate lookups:
// BAD
{
'postId': 'post123',
'authorId': 'user456' // Requires another read
}
// GOOD - Include frequently needed data
{
'postId': 'post123',
'authorId': 'user456',
'authorName': 'John Doe',
'authorAvatar': 'url...'
}
// 2. Use subcollections for large lists
// BAD - Array in document (1MB limit, inefficient updates)
{
'userId': 'user123',
'messages': [...hundreds of messages]
}
// GOOD - Subcollection
// users/user123/messages/msg1
// users/user123/messages/msg2
// 3. Avoid deeply nested data
// BAD
users -> userId -> posts -> postId -> comments -> commentId -> replies
// GOOD - Flatten or use collection groups
comments (with postId, userId fields)Cost Optimization:
class CostOptimizationService {
// 1. Use offline persistence
void enableOfflinePersistence() {
FirebaseFirestore.instance.settings = Settings(
persistenceEnabled: true,
cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);
}
// 2. Limit query results
Future<QuerySnapshot> getRecentPosts() {
return FirebaseFirestore.instance
.collection('posts')
.orderBy('createdAt', descending: true)
.limit(20) // Always use limit!
.get();
}
// 3. Use select() to fetch only needed fields
Future<QuerySnapshot> getUserNames() {
return FirebaseFirestore.instance
.collection('users')
.select(['name', 'avatar']) // Only fetches these fields
.get();
}
// 4. Cache frequently accessed data
final Map<String, dynamic> _cache = {};
Future<Map<String, dynamic>?> getCachedUser(String userId) async {
if (_cache.containsKey(userId)) {
return _cache[userId];
}
DocumentSnapshot doc = await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.get();
if (doc.exists) {
_cache[userId] = doc.data();
return _cache[userId];
}
return null;
}
// 5. Use batch writes to reduce operations
Future<void> batchUpdate(List<String> docIds, Map<String, dynamic> data) async {
WriteBatch batch = FirebaseFirestore.instance.batch();
for (String id in docIds) {
batch.update(
FirebaseFirestore.instance.collection('items').doc(id),
data,
);
}
await batch.commit(); // Single operation cost
}
// 6. Listen carefully - unsubscribe when not needed
StreamSubscription? _subscription;
void startListening() {
_subscription = FirebaseFirestore.instance
.collection('updates')
.snapshots()
.listen((snapshot) {});
}
void stopListening() {
_subscription?.cancel(); // Stop billing for reads
}
}Key Cost-Saving Tips:
- Always use
.limit()on queries - Use offline persistence to reduce reads
- Cache data client-side when appropriate
- Unsubscribe from listeners when not needed
- Use batch operations for multiple writes
- Structure data to minimize reads
- Use security rules to prevent unnecessary operations