Firebase Integration

Full backend services: Auth, Firestore, Storage, Push Notifications, Analytics, Crashlytics & Remote Config. Includes Provider integration.

Sby Skills Guide Bot
DevelopmentIntermediate
007/22/2026
Claude CodeCursorWindsurfCopilotCodex
#firebase#firestore#auth#cloud-messaging#analytics

Recommended for

🔥 Skill: Firebase Integration

📋 Metadata

| Atributo | Valor | |----------|-------| | ID | flutter-firebase | | Nivel | 🟡 Intermedio | | Versión | 2.0.0 | | Keywords | firebase, firestore, auth, cloud-messaging, analytics, storage, remote-config, crashlytics, provider | | Referencia | FlutterFire |

🔑 Keywords para Invocación

  • firebase
  • firestore
  • firebase-auth
  • cloud-messaging
  • firebase-analytics
  • firebase-storage
  • firebase-remote-config
  • firebase-crashlytics
  • provider
  • @skill:firebase

Ejemplos de Prompts

Integra Firebase con auth y Firestore
Implementa Firebase Authentication y Cloud Messaging
@skill:firebase - Configura Firebase completo

📖 Descripción

Firebase Integration proporciona servicios backend completos: Authentication (Email/Password y Google Sign-In), Firestore Database, Cloud Storage, Push Notifications (FCM), Analytics (con tracking de screens, eventos personalizados, user ID y propiedades), Crashlytics y Remote Config. Incluye integración con Provider para state management y configuración multiplataforma con mejores prácticas.

⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.

⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.

✅ Cuándo Usar Este Skill

  • Backend as a Service rápido
  • Authentication con múltiples providers
  • Base de datos en tiempo real
  • Push notifications
  • Analytics y Crashlytics
  • Remote Config para A/B testing
  • Rapid prototyping

❌ Cuándo NO Usar Este Skill

  • Requieres control total del backend
  • Costos de Firebase son prohibitivos
  • Backend custom ya existe

🏗️ Estructura del Proyecto

lib/
├── core/
│   ├── firebase/
│   │   ├── firebase_options.dart (generado)
│   │   ├── firebase_config.dart
│   │   └── firebase_initialization.dart
│   └── services/
│       ├── analytics_service.dart
│       ├── crashlytics_service.dart
│       ├── remote_config_service.dart
│       └── storage_service.dart
│
├── features/
│   ├── authentication/
│   │   ├── data/
│   │   │   ├── datasources/
│   │   │   │   └── firebase_auth_datasource.dart
│   │   │   └── repositories/
│   │   │       └── auth_repository_impl.dart
│   │   ├── domain/
│   │   │   ├── entities/
│   │   │   │   └── user.dart
│   │   │   └── repositories/
│   │   │       └── auth_repository.dart
│   │   └── presentation/
│   │       ├── screens/
│   │       │   └── login_screen.dart
│   │       ├── providers/
│   │       │   └── auth_provider.dart
│   │       └── bloc/
│   │           └── auth_bloc.dart
│   │
│   ├── products/
│   │   ├── data/
│   │   │   ├── datasources/
│   │   │   │   └── firestore_products_datasource.dart
│   │   │   └── models/
│   │   │       └── product_model.dart
│   │   └── domain/
│   │       └── entities/
│   │           └── product.dart
│   │
│   └── notifications/
│       ├── data/
│       │   ├── datasources/
│       │   │   └── fcm_datasource.dart
│       │   └── services/
│       │       └── notification_service.dart
│       └── presentation/
│           └── screens/
│               └── notifications_screen.dart
│
└── main.dart

📦 Dependencias Requeridas

dependencies:
  flutter:
    sdk: flutter

  # Firebase Core
  firebase_core: ^2.24.2

  # Firebase Authentication
  firebase_auth: ^4.15.3
  google_sign_in: ^6.2.1

  # Cloud Firestore
  cloud_firestore: ^4.13.6

  # Cloud Storage
  firebase_storage: ^11.5.6

  # Cloud Messaging
  firebase_messaging: ^14.7.9
  flutter_local_notifications: ^16.3.0

  # Firebase Analytics
  firebase_analytics: ^10.7.4

  # Crashlytics
  firebase_crashlytics: ^3.4.8

  # Remote Config
  firebase_remote_config: ^4.3.8

  # State Management
  provider: ^6.1.1

  # Image Picker (para Storage)
  image_picker: ^1.0.7

  # Utils
  equatable: ^2.0.5
  dartz: ^0.10.1

dev_dependencies:
  flutter_test:
    sdk: flutter

⚙️ Configuración Inicial

1. Firebase CLI Setup

# Instalar Firebase CLI
npm install -g firebase-tools

# Login a Firebase
firebase login

# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
    echo "Error: Ejecuta este comando desde la raíz del proyecto"
    exit 1
fi

# Instalar FlutterFire CLI
dart pub global activate flutterfire_cli

# Configurar Firebase para el proyecto
cd mobile
flutterfire configure
cd ..
flutterfire configure

2. Android Configuration

// android/build.gradle
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.0'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
    }
}
// android/app/build.gradle
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

android {
    defaultConfig {
        minSdkVersion 21  // Firebase requires 21+
    }
}

3. iOS Configuration

# ios/Podfile
platform :ios, '13.0'  # Firebase requires 13.0+

# Después de flutter_install_all_ios_pods
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
    end
  end
end

💻 Implementación

1. Firebase Initialization

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Firebase
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // Pass all uncaught errors to Crashlytics
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;

  runApp(const MyApp());
}

2. Firebase Authentication

// lib/features/authentication/data/datasources/firebase_auth_datasource.dart
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import 'package:google_sign_in/google_sign_in.dart';
import '../models/user_model.dart';

abstract class FirebaseAuthDataSource {
  Stream<UserModel?> get authStateChanges;
  Future<UserModel> signInWithEmailAndPassword(String email, String password);
  Future<UserModel> signUpWithEmailAndPassword(String email, String password);
  Future<UserModel> signInWithGoogle();
  Future<void> signOut();
  Future<void> sendPasswordResetEmail(String email);
  UserModel? getCurrentUser();
}

class FirebaseAuthDataSourceImpl implements FirebaseAuthDataSource {
  final firebase_auth.FirebaseAuth _firebaseAuth;
  final GoogleSignIn _googleSignIn;

  FirebaseAuthDataSourceImpl({
    firebase_auth.FirebaseAuth? firebaseAuth,
    GoogleSignIn? googleSignIn,
  })  : _firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance,
        _googleSignIn = googleSignIn ?? GoogleSignIn();

  @override
  Stream<UserModel?> get authStateChanges {
    return _firebaseAuth.authStateChanges().map((firebaseUser) {
      return firebaseUser != null ? UserModel.fromFirebaseUser(firebaseUser) : null;
    });
  }

  @override
  Future<UserModel> signInWithEmailAndPassword(
    String email,
    String password,
  ) async {
    try {
      final credential = await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );

      if (credential.user == null) {
        throw Exception('Sign in failed');
      }

      return UserModel.fromFirebaseUser(credential.user!);
    } on firebase_auth.FirebaseAuthException catch (e) {
      throw _handleAuthException(e);
    }
  }

  @override
  Future<UserModel> signUpWithEmailAndPassword(
    String email,
    String password,
  ) async {
    try {
      final credential = await _firebaseAuth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );

      if (credential.user == null) {
        throw Exception('Sign up failed');
      }

      return UserModel.fromFirebaseUser(credential.user!);
    } on firebase_auth.FirebaseAuthException catch (e) {
      throw _handleAuthException(e);
    }
  }

  @override
  Future<UserModel> signInWithGoogle() async {
    try {
      // Trigger the authentication flow
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();

      if (googleUser == null) {
        throw Exception('Google sign in was cancelled');
      }

      // Obtain the auth details from the request
      final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

      // Create a new credential
      final credential = firebase_auth.GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // Sign in to Firebase with the Google credential
      final userCredential = await _firebaseAuth.signInWithCredential(credential);

      if (userCredential.user == null) {
        throw Exception('Google sign in failed');
      }

      return UserModel.fromFirebaseUser(userCredential.user!);
    } on firebase_auth.FirebaseAuthException catch (e) {
      throw _handleAuthException(e);
    } catch (e) {
      throw Exception('Google sign in failed: $e');
    }
  }

  @override
  Future<void> signOut() async {
    await Future.wait([
      _firebaseAuth.signOut(),
      _googleSignIn.signOut(),
    ]);
  }

  @override
  Future<void> sendPasswordResetEmail(String email) async {
    try {
      await _firebaseAuth.sendPasswordResetEmail(email: email);
    } on firebase_auth.FirebaseAuthException catch (e) {
      throw _handleAuthException(e);
    }
  }

  @override
  UserModel? getCurrentUser() {
    final firebaseUser = _firebaseAuth.currentUser;
    return firebaseUser != null ? UserModel.fromFirebaseUser(firebaseUser) : null;
  }

  Exception _handleAuthException(firebase_auth.FirebaseAuthException e) {
    switch (e.code) {
      case 'user-not-found':
        return Exception('No user found with this email');
      case 'wrong-password':
        return Exception('Wrong password');
      case 'email-already-in-use':
        return Exception('Email already in use');
      case 'weak-password':
        return Exception('Password is too weak');
      case 'invalid-email':
        return Exception('Invalid email format');
      default:
        return Exception('Authentication failed: ${e.message}');
    }
  }
}

3. Cloud Firestore

// lib/features/products/data/datasources/firestore_products_datasource.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/product_model.dart';

abstract class FirestoreProductsDataSource {
  Stream<List<ProductModel>> getProductsStream();
  Future<List<ProductModel>> getProducts();
  Future<ProductModel> getProduct(String id);
  Future<ProductModel> createProduct(ProductModel product);
  Future<ProductModel> updateProduct(ProductModel product);
  Future<void> deleteProduct(String id);
}

class FirestoreProductsDataSourceImpl implements FirestoreProductsDataSource {
  final FirebaseFirestore _firestore;
  static const String _collection = 'products';

  FirestoreProductsDataSourceImpl({FirebaseFirestore? firestore})
      : _firestore = firestore ?? FirebaseFirestore.instance;

  @override
  Stream<List<ProductModel>> getProductsStream() {
    return _firestore
        .collection(_collection)
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map((snapshot) {
      return snapshot.docs
          .map((doc) => ProductModel.fromFirestore(doc))
          .toList();
    });
  }

  @override
  Future<List<ProductModel>> getProducts() async {
    final snapshot = await _firestore
        .collection(_collection)
        .orderBy('createdAt', descending: true)
        .get();

    return snapshot.docs
        .map((doc) => ProductModel.fromFirestore(doc))
        .toList();
  }

  @override
  Future<ProductModel> getProduct(String id) async {
    final doc = await _firestore.collection(_collection).doc(id).get();

    if (!doc.exists) {
      throw Exception('Product not found');
    }

    return ProductModel.fromFirestore(doc);
  }

  @override
  Future<ProductModel> createProduct(ProductModel product) async {
    final docRef = _firestore.collection(_collection).doc();

    final productWithId = product.copyWith(
      id: docRef.id,
      createdAt: DateTime.now(),
      updatedAt: DateTime.now(),
    );

    await docRef.set(productWithId.toFirestore());

    return productWithId;
  }

  @override
  Future<ProductModel> updateProduct(ProductModel product) async {
    final productWithTimestamp = product.copyWith(
      updatedAt: DateTime.now(),
    );

    await _firestore
        .collection(_collection)
        .doc(product.id)
        .update(productWithTimestamp.toFirestore());

    return productWithTimestamp;
  }

  @override
  Future<void> deleteProduct(String id) async {
    await _firestore.collection(_collection).doc(id).delete();
  }
}

// ProductModel extension for Firestore
extension ProductModelFirestore on ProductModel {
  static ProductModel fromFirestore(DocumentSnapshot doc) {
    final data = doc.data() as Map<String, dynamic>;

    return ProductModel(
      id: doc.id,
      name: data['name'] as String,
      description: data['description'] as String,
      price: (data['price'] as num).toDouble(),
      imageUrl: data['imageUrl'] as String,
      stock: data['stock'] as int,
      createdAt: (data['createdAt'] as Timestamp).toDate(),
      updatedAt: (data['updatedAt'] as Timestamp).toDate(),
    );
  }

  Map<String, dynamic> toFirestore() {
    return {
      'name': name,
      'description': description,
      'price': price,
      'imageUrl': imageUrl,
      'stock': stock,
      'createdAt': Timestamp.fromDate(createdAt),
      'updatedAt': Timestamp.fromDate(updatedAt),
    };
  }
}

4. Cloud Messaging (Push Notifications)

// lib/features/notifications/data/services/fcm_service.dart
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

// Handler para mensajes en background
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print('Handling background message: ${message.messageId}');
}

class FCMService {
  final FirebaseMessaging _messaging;
  final FlutterLocalNotificationsPlugin _localNotifications;

  FCMService({
    FirebaseMessaging? messaging,
    FlutterLocalNotificationsPlugin? localNotifications,
  })  : _messaging = messaging ?? FirebaseMessaging.instance,
        _localNotifications =
            localNotifications ?? FlutterLocalNotificationsPlugin();

  Future<void> initialize() async {
    // Request permission (iOS)
    await _requestPermission();

    // Initialize local notifications
    await _initializeLocalNotifications();

    // Get FCM token
    final token = await _messaging.getToken();
    print('FCM Token: $token');

    // Listen to token refresh
    _messaging.onTokenRefresh.listen((newToken) {
      print('FCM Token refreshed: $newToken');
      // Send token to your backend
    });

    // Handle foreground messages
    FirebaseMessaging.onMessage.listen(_handleForegroundMessage);

    // Handle notification tap when app is in background
    FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);

    // Handle notification tap when app is terminated
    final initialMessage = await _messaging.getInitialMessage();
    if (initialMessage != null) {
      _handleNotificationTap(initialMessage);
    }

    // Set background handler
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  }

  Future<void> _requestPermission() async {
    final settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
      provisional: false,
    );

    print('Permission granted: ${settings.authorizationStatus}');
  }

  Future<void> _initializeLocalNotifications() async {
    const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    const iosSettings = DarwinInitializationSettings();

    const settings = InitializationSettings(
      android: androidSettings,
      iOS: iosSettings,
    );

    await _localNotifications.initialize(
      settings,
      onDidReceiveNotificationResponse: (details) {
        print('Notification tapped: ${details.payload}');
      },
    );

    // Create notification channel (Android)
    if (Platform.isAndroid) {
      const channel = AndroidNotificationChannel(
        'high_importance_channel',
        'High Importance Notifications',
        description: 'This channel is used for important notifications.',
        importance: Importance.high,
      );

      await _localNotifications
          .resolvePlatformSpecificImplementation<
              AndroidFlutterLocalNotificationsPlugin>()
          ?.createNotificationChannel(channel);
    }
  }

  Future<void> _handleForegroundMessage(RemoteMessage message) async {
    print('Foreground message: ${message.messageId}');

    // Show local notification
    await _showLocalNotification(message);
  }

  Future<void> _showLocalNotification(RemoteMessage message) async {
    final notification = message.notification;
    if (notification == null) return;

    await _localNotifications.show(
      notification.hashCode,
      notification.title,
      notification.body,
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'high_importance_channel',
          'High Importance Notifications',
          importance: Importance.high,
          priority: Priority.high,
        ),
        iOS: DarwinNotificationDetails(),
      ),
      payload: message.data.toString(),
    );
  }

  void _handleNotificationTap(RemoteMessage message) {
    print('Notification tapped: ${message.messageId}');
    // Navigate to specific screen based on message data
  }

  Future<String?> getToken() async {
    return await _messaging.getToken();
  }

  Future<void> subscribeToTopic(String topic) async {
    await _messaging.subscribeToTopic(topic);
  }

  Future<void> unsubscribeFromTopic(String topic) async {
    await _messaging.unsubscribeFromTopic(topic);
  }
}

5. Firebase Analytics

Firebase Analytics permite rastrear el comportamiento de los usuarios, eventos personalizados, nombres de pantallas, user ID y propiedades de usuario.

Características principales:

  • ✅ Tracking automático de nombres de pantallas
  • ✅ Eventos personalizados con parámetros
  • ✅ Configuración de User ID
  • ✅ Propiedades de usuario personalizadas
// lib/core/services/analytics_service.dart
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';

class AnalyticsService {
  final FirebaseAnalytics _analytics;

  AnalyticsService({FirebaseAnalytics? analytics})
      : _analytics = analytics ?? FirebaseAnalytics.instance;

  // Observer para tracking automático de screens
  FirebaseAnalyticsObserver get observer =>
      FirebaseAnalyticsObserver(analytics: _analytics);

  // Track screen view con nombre personalizado
  Future<void> logScreenView({
    required String screenName,
    String? screenClass,
  }) async {
    await _analytics.logScreenView(
      screenName: screenName,
      screenClass: screenClass ?? screenName,
    );
  }

  // Track custom events con parámetros
  Future<void> logCustomEvent({
    required String eventName,
    Map<String, Object?>? parameters,
  }) async {
    await _analytics.logEvent(
      name: eventName,
      parameters: parameters,
    );
  }

  // Set user ID (debe llamarse después del login)
  Future<void> setUserId(String userId) async {
    await _analytics.setUserId(id: userId);
  }

  // Clear user ID (debe llamarse en logout)
  Future<void> clearUserId() async {
    await _analytics.setUserId(id: null);
  }

  // Set user properties (máximo 25 propiedades)
  Future<void> setUserProperty({
    required String name,
    required String? value,
  }) async {
    await _analytics.setUserProperty(name: name, value: value);
  }

  // Set multiple user properties
  Future<void> setUserProperties(Map<String, String?> properties) async {
    for (final entry in properties.entries) {
      await setUserProperty(name: entry.key, value: entry.value);
    }
  }

  // E-commerce events
  Future<void> logAddToCart({
    required String productId,
    required String productName,
    required double price,
    String currency = 'USD',
  }) async {
    await _analytics.logAddToCart(
      currency: currency,
      value: price,
      items: [
        AnalyticsEventItem(
          itemId: productId,
          itemName: productName,
          price: price,
        ),
      ],
    );
  }

  Future<void> logPurchase({
    required double value,
    required List<AnalyticsEventItem> items,
    String currency = 'USD',
    String? transactionId,
  }) async {
    await _analytics.logPurchase(
      currency: currency,
      value: value,
      items: items,
      transactionId: transactionId,
    );
  }

  // Authentication events
  Future<void> logLogin({required String method}) async {
    await _analytics.logLogin(loginMethod: method);
  }

  Future<void> logSignUp({required String method}) async {
    await _analytics.logSignUp(signUpMethod: method);
  }
}

// Uso con NavigatorObserver para tracking automático de screens
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final analyticsService = AnalyticsService();

    return MaterialApp(
      title: 'Firebase App',
      navigatorObservers: [
        analyticsService.observer, // Tracking automático de screens
      ],
      home: const HomeScreen(),
    );
  }
}

// Ejemplo de uso en un widget
class ProductScreen extends StatelessWidget {
  const ProductScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final analytics = AnalyticsService();

    // Track screen view manualmente (si no usas observer)
    WidgetsBinding.instance.addPostFrameCallback((_) {
      analytics.logScreenView(screenName: 'product_detail');
    });

    return Scaffold(
      appBar: AppBar(title: const Text('Product')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Track custom event
            analytics.logCustomEvent(
              eventName: 'product_viewed',
              parameters: {
                'product_id': '123',
                'product_name': 'Example Product',
                'product_category': 'Electronics',
              },
            );
          },
          child: const Text('View Product'),
        ),
      ),
    );
  }
}

// Ejemplo de configuración de user properties después del login
class AuthService {
  final AnalyticsService _analytics = AnalyticsService();

  Future<void> onUserLogin(String userId, Map<String, String> userData) async {
    // Set user ID
    await _analytics.setUserId(userId);

    // Set user properties
    await _analytics.setUserProperties({
      'user_type': userData['type'] ?? 'regular',
      'subscription_status': userData['subscription'] ?? 'free',
      'sign_up_method': userData['method'] ?? 'email',
    });

    // Log login event
    await _analytics.logLogin(method: userData['method'] ?? 'email');
  }

  Future<void> onUserLogout() async {
    // Clear user ID
    await _analytics.clearUserId();
  }
}

6. Crashlytics

// lib/core/services/crashlytics_service.dart
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';

class CrashlyticsService {
  final FirebaseCrashlytics _crashlytics;

  CrashlyticsService({FirebaseCrashlytics? crashlytics})
      : _crashlytics = crashlytics ?? FirebaseCrashlytics.instance;

  Future<void> initialize() async {
    // Enable collection in production only
    await _crashlytics.setCrashlyticsCollectionEnabled(!kDebugMode);
  }

  void logError(dynamic exception, StackTrace? stackTrace, {String? reason}) {
    _crashlytics.recordError(
      exception,
      stackTrace,
      reason: reason,
      fatal: false,
    );
  }

  void logFatalError(dynamic exception, StackTrace stackTrace, {String? reason}) {
    _crashlytics.recordError(
      exception,
      stackTrace,
      reason: reason,
      fatal: true,
    );
  }

  Future<void> log(String message) async {
    await _crashlytics.log(message);
  }

  Future<void> setCustomKey(String key, Object value) async {
    await _crashlytics.setCustomKey(key, value);
  }

  Future<void> setUserId(String userId) async {
    await _crashlytics.setUserIdentifier(userId);
  }
}

7. Remote Config

// lib/core/services/remote_config_service.dart
import 'package:firebase_remote_config/firebase_remote_config.dart';

class RemoteConfigService {
  final FirebaseRemoteConfig _remoteConfig;

  RemoteConfigService({FirebaseRemoteConfig? remoteConfig})
      : _remoteConfig = remoteConfig ?? FirebaseRemoteConfig.instance;

  Future<void> initialize() async {
    await _remoteConfig.setConfigSettings(
      RemoteConfigSettings(
        fetchTimeout: const Duration(seconds: 10),
        minimumFetchInterval: const Duration(hours: 1),
      ),
    );

    // Set default values
    await _remoteConfig.setDefaults({
      'feature_enabled': false,
      'max_items_per_page': 20,
      'app_version': '1.0.0',
      'maintenance_mode': false,
    });

    // Fetch and activate
    await fetchAndActivate();
  }

  Future<bool> fetchAndActivate() async {
    try {
      final fetched = await _remoteConfig.fetchAndActivate();
      return fetched;
    } catch (e) {
      print('Error fetching remote config: $e');
      return false;
    }
  }

  // Get values
  bool getBool(String key) => _remoteConfig.getBool(key);
  String getString(String key) => _remoteConfig.getString(key);
  int getInt(String key) => _remoteConfig.getInt(key);
  double getDouble(String key) => _remoteConfig.getDouble(key);

  // Listen to config updates
  Stream<void> get onConfigUpdated => _remoteConfig.onConfigUpdated;

  // Force fetch
  Future<void> fetch() async {
    await _remoteConfig.fetch();
  }

  // Activate fetched values
  Future<bool> activate() async {
    return await _remoteConfig.activate();
  }
}

8. Firebase Storage

// lib/core/services/storage_service.dart
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';

class StorageService {
  final FirebaseStorage _storage;

  StorageService({FirebaseStorage? storage})
      : _storage = storage ?? FirebaseStorage.instance;

  // Upload file
  Future<String> uploadFile({
    required File file,
    required String path,
    String? fileName,
    Function(double)? onProgress,
  }) async {
    try {
      final ref = _storage.ref(path).child(fileName ?? file.path.split('/').last);

      final uploadTask = ref.putFile(
        file,
        SettableMetadata(
          contentType: _getContentType(file.path),
          customMetadata: {
            'uploaded_at': DateTime.now().toIso8601String(),
          },
        ),
      );

      // Listen to upload progress
      uploadTask.snapshotEvents.listen((taskSnapshot) {
        final progress = taskSnapshot.bytesTransferred / taskSnapshot.totalBytes;
        onProgress?.call(progress);
      });

      final snapshot = await uploadTask;
      final downloadUrl = await snapshot.ref.getDownloadURL();

      return downloadUrl;
    } on FirebaseException catch (e) {
      throw Exception('Upload failed: ${e.message}');
    }
  }

  // Upload image from image picker
  Future<String> uploadImage({
    required XFile image,
    required String path,
    Function(double)? onProgress,
  }) async {
    return await uploadFile(
      file: File(image.path),
      path: path,
      fileName: image.name,
      onProgress: onProgress,
    );
  }

  // Download file
  Future<File> downloadFile({
    required String url,
    required String localPath,
  }) async {
    try {
      final ref = _storage.refFromURL(url);
      final file = File(localPath);

      await ref.writeToFile(file);
      return file;
    } on FirebaseException catch (e) {
      throw Exception('Download failed: ${e.message}');
    }
  }

  // Get download URL
  Future<String> getDownloadUrl(String path) async {
    try {
      return await _storage.ref(path).getDownloadURL();
    } on FirebaseException catch (e) {
      throw Exception('Failed to get download URL: ${e.message}');
    }
  }

  // Delete file
  Future<void> deleteFile(String path) async {
    try {
      await _storage.ref(path).delete();
    } on FirebaseException catch (e) {
      throw Exception('Delete failed: ${e.message}');
    }
  }

  // List files in a path
  Future<List<Reference>> listFiles(String path) async {
    try {
      final listResult = await _storage.ref(path).listAll();
      return listResult.items;
    } on FirebaseException catch (e) {
      throw Exception('List failed: ${e.message}');
    }
  }

  // Get file metadata
  Future<FullMetadata> getMetadata(String path) async {
    try {
      return await _storage.ref(path).getMetadata();
    } on FirebaseException catch (e) {
      throw Exception('Failed to get metadata: ${e.message}');
    }
  }

  String _getContentType(String filePath) {
    final extension = filePath.split('.').last.toLowerCase();
    switch (extension) {
      case 'jpg':
      case 'jpeg':
        return 'image/jpeg';
      case 'png':
        return 'image/png';
      case 'gif':
        return 'image/gif';
      case 'pdf':
        return 'application/pdf';
      case 'mp4':
        return 'video/mp4';
      default:
        return 'application/octet-stream';
    }
  }
}

9. Provider State Management con Firebase

// lib/features/authentication/presentation/providers/auth_provider.dart
import 'package:flutter/foundation.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import '../../domain/entities/user.dart';
import '../../data/repositories/auth_repository_impl.dart';

class AuthProvider with ChangeNotifier {
  final AuthRepositoryImpl _authRepository;

  User? _user;
  bool _isLoading = false;
  String? _error;

  AuthProvider({required AuthRepositoryImpl authRepository})
      : _authRepository = authRepository {
    _init();
  }

  User? get user => _user;
  bool get isLoading => _isLoading;
  String? get error => _error;
  bool get isAuthenticated => _user != null;

  void _init() {
    // Listen to auth state changes
    _authRepository.authStateChanges.listen((user) {
      _user = user;
      _error = null;
      notifyListeners();
    });
  }

  Future<void> signInWithEmailAndPassword(String email, String password) async {
    try {
      _isLoading = true;
      _error = null;
      notifyListeners();

      _user = await _authRepository.signInWithEmailAndPassword(email, password);
    } catch (e) {
      _error = e.toString();
      _user = null;
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> signUpWithEmailAndPassword(String email, String password) async {
    try {
      _isLoading = true;
      _error = null;
      notifyListeners();

      _user = await _authRepository.signUpWithEmailAndPassword(email, password);
    } catch (e) {
      _error = e.toString();
      _user = null;
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> signInWithGoogle() async {
    try {
      _isLoading = true;
      _error = null;
      notifyListeners();

      _user = await _authRepository.signInWithGoogle();
    } catch (e) {
      _error = e.toString();
      _user = null;
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> signOut() async {
    try {
      _isLoading = true;
      notifyListeners();

      await _authRepository.signOut();
      _user = null;
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> sendPasswordResetEmail(String email) async {
    try {
      _isLoading = true;
      _error = null;
      notifyListeners();

      await _authRepository.sendPasswordResetEmail(email);
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

// lib/main.dart - Provider setup
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'features/authentication/presentation/providers/auth_provider.dart';
import 'features/authentication/data/repositories/auth_repository_impl.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => AuthProvider(
            authRepository: AuthRepositoryImpl(),
          ),
        ),
        // Add more providers here
      ],
      child: MaterialApp(
        title: 'Firebase App',
        home: const HomeScreen(),
      ),
    );
  }
}

// Usage in widget
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final authProvider = Provider.of<AuthProvider>(context);

    if (authProvider.isLoading) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }

    if (authProvider.isAuthenticated) {
      return Scaffold(
        appBar: AppBar(title: const Text('Home')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Welcome, ${authProvider.user?.email}'),
              ElevatedButton(
                onPressed: () => authProvider.signOut(),
                child: const Text('Sign Out'),
              ),
            ],
          ),
        ),
      );
    }

    return const LoginScreen();
  }
}

🎯 Mejores Prácticas

1. Security Rules (Firestore)

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Products collection
    match /products/{productId} {
      allow read: if true;
      allow write: if request.auth != null;
    }

    // Users collection
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

2. Offline Persistence

// Enable offline persistence
await FirebaseFirestore.instance.enablePersistence(
  const PersistenceSettings(synchronizeTabs: true),
);

3. Error Handling

try {
  await _firestore.collection('products').add(data);
} on FirebaseException catch (e) {
  if (e.code == 'permission-denied') {
    // Handle permission error
  } else if (e.code == 'unavailable') {
    // Handle offline error
  }
}

4. Firebase Storage Security Rules

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // User uploads - only authenticated users can upload
    match /users/{userId}/{allPaths=**} {
      allow read: if true;
      allow write: if request.auth != null && request.auth.uid == userId;
    }

    // Public images
    match /public/{allPaths=**} {
      allow read: if true;
      allow write: if request.auth != null;
    }

    // Private files
    match /private/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

5. Remote Config Best Practices

// Configurar valores por defecto locales
await _remoteConfig.setDefaults({
  'feature_enabled': false,
  'max_items': 20,
  'api_endpoint': 'https://api.example.com',
});

// Fetch en intervalos apropiados
// - Development: 0 segundos (fetch inmediato)
// - Production: 1 hora (fetch cada hora)
await _remoteConfig.setConfigSettings(
  RemoteConfigSettings(
    fetchTimeout: const Duration(seconds: 10),
    minimumFetchInterval: kDebugMode
      ? const Duration(seconds: 0)
      : const Duration(hours: 1),
  ),
);

// Siempre activar después de fetch
final fetched = await _remoteConfig.fetchAndActivate();
if (fetched) {
  print('Remote config updated');
}

6. Analytics Best Practices

// 1. Configurar user ID después del login
await analytics.setUserId(userId);

// 2. Configurar user properties (máximo 25)
await analytics.setUserProperties({
  'user_type': 'premium',
  'subscription_status': 'active',
  'sign_up_method': 'google',
});

// 3. Usar nombres consistentes para screens
await analytics.logScreenView(screenName: 'product_detail');

// 4. Usar eventos predefinidos cuando sea posible
await analytics.logLogin(method: 'google');
await analytics.logSignUp(method: 'email');

// 5. Limpiar user ID en logout
await analytics.clearUserId();

7. Provider State Management Best Practices

// 1. Usar ChangeNotifierProvider para estado local
ChangeNotifierProvider(
  create: (_) => AuthProvider(authRepository: AuthRepositoryImpl()),
)

// 2. Usar Consumer para rebuilds selectivos
Consumer<AuthProvider>(
  builder: (context, authProvider, child) {
    if (authProvider.isLoading) {
      return const CircularProgressIndicator();
    }
    return Text('Welcome, ${authProvider.user?.email}');
  },
)

// 3. Usar Selector para optimizar rebuilds
Selector<AuthProvider, bool>(
  selector: (_, provider) => provider.isAuthenticated,
  builder: (context, isAuthenticated, child) {
    return isAuthenticated ? const HomeScreen() : const LoginScreen();
  },
)

// 4. Separar providers por feature
MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => AuthProvider(...)),
    ChangeNotifierProvider(create: (_) => ProductProvider(...)),
    ChangeNotifierProvider(create: (_) => CartProvider(...)),
  ],
  child: MyApp(),
)

📚 Recursos Adicionales

🔗 Skills Relacionados


Versión: 2.0.0 Última actualización: Diciembre 2025

📝 Changelog

v2.0.0 (Diciembre 2025)

  • ✅ Agregada sección completa de Firebase Storage con upload, download y gestión de archivos
  • ✅ Agregada implementación detallada de Remote Config con valores por defecto y fetch
  • ✅ Agregada integración con Provider para state management
  • ✅ Mejorada sección de Analytics con ejemplos de screen tracking, custom events, user ID y user properties
  • ✅ Agregadas mejores prácticas para Storage, Remote Config, Analytics y Provider
  • ✅ Actualizada estructura del proyecto para incluir nuevos servicios
Related skills