Skip to content

Latest commit

 

History

History
2695 lines (2178 loc) · 67 KB

File metadata and controls

2695 lines (2178 loc) · 67 KB

Flutter Firebase: Answers

Firebase Setup and Configuration

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 configure

This command:

  1. Creates a new Firebase project or uses existing one
  2. Registers your app for each platform (iOS, Android, Web)
  3. Generates firebase_options.dart with all configurations
  4. 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 needed

2. 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 Firebase

iOS (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');
}

Firebase Authentication

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.0
import '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.0
import '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();
  }
}

Cloud Firestore

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, returns Future
  • snapshots(): Continuous updates, returns Stream

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 where clauses on different fields
  • where clause with orderBy on 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:

  1. Automatic: Run the query, click the error link
  2. Manual: Firebase Console → Firestore → Indexes
  3. 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';
  }
}

Firebase Realtime Database

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);
  }
}

Firebase Storage

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.0
import '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,
    );
  }
}

Cloud Functions

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;
      }
    }
  }
}

Firebase Cloud Messaging

39. How do you set up Firebase Cloud Messaging (FCM) in a Flutter app?

# pubspec.yaml
dependencies:
  firebase_messaging: ^14.7.0
import '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');
    }
  }
}

Firebase Analytics

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);
  }
}

Firebase Crashlytics

45. How do you set up Firebase Crashlytics in Flutter?

# pubspec.yaml
dependencies:
  firebase_crashlytics: ^3.4.0
import '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;
    }
  }
}

Firebase Remote Config

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'),
    };
  }
}

Security and Best Practices

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:

  1. Always use .limit() on queries
  2. Use offline persistence to reduce reads
  3. Cache data client-side when appropriate
  4. Unsubscribe from listeners when not needed
  5. Use batch operations for multiple writes
  6. Structure data to minimize reads
  7. Use security rules to prevent unnecessary operations