flutter_firebase_push_notification_provider_web 3.0.0 copy "flutter_firebase_push_notification_provider_web: ^3.0.0" to clipboard
flutter_firebase_push_notification_provider_web: ^3.0.0 copied to clipboard

Platformweb

A Flutter package for managing Firebase Cloud Messaging (FCM) push notifications for web applications. Handles permissions, token management, and message routing.

Flutter Firebase Push Notification Provider #

Un paquete Flutter que proporciona una interfaz unificada y optimizada para gestionar notificaciones push de Firebase Cloud Messaging (FCM). Maneja permisos, tokens y mensajes en todos los estados de la aplicación (foreground, background y terminada) con gestión automática de memoria y prevención de leaks.

Features #

  • ✅ Manejo automático de notificaciones en foreground, background y cuando la app está cerrada
  • ✅ Stream unificado para recibir todas las notificaciones
  • ✅ Gestión automática de permisos de notificación
  • ✅ Actualización automática de tokens FCM en el servidor MongoDB
  • ✅ Filtrado de mensajes por IdPushNotification
  • ✅ Prevención de memory leaks con gestión apropiada de subscriptions
  • ✅ Protección contra inicialización múltiple
  • ✅ Manejo robusto de errores con propagación apropiada
  • ✅ Integración automática con información del dispositivo y versión de la app
  • ✅ Debug logging integrado con formato consistente
  • ✅ Verificación de tokens existentes para evitar duplicados
  • ✅ Gestión de estados de registros (activo/bloqueado)
  • ✅ Anulación de tokens con eliminación de FCM y actualización de estado

Getting started #

Prerequisitos #

  • Flutter SDK >= 1.17.0
  • Dart SDK >= 3.3.2
  • Firebase configurado en tu proyecto (Guía de configuración)
  • Servidor MongoDB configurado con la colección requerida (ver sección "Configuración del Servidor MongoDB")
  • Paquetes adicionales requeridos (disponibles en pub.flutter-io.cn):
    • api_rest_flutter_web
    • flutter_utils_providers
    • flutter_models_provider

Instalación #

Agrega el paquete a tu pubspec.yaml:

dependencies:
  flutter_firebase_push_notification_provider_web: ^3.0.0

Luego ejecuta:

flutter pub get

Configuración del Servidor MongoDB #

⚠️ REQUISITO FUNDAMENTAL #

Antes de usar este paquete, es requisito fundamental que tu servidor MongoDB tenga configurada la siguiente colección según se define en EnvironmentPushNotification:

Nombre de la Colección

tokenFirebase

Estructura de la Colección MongoDB

La colección debe tener los siguientes campos en el orden especificado:

Campo Tipo Descripción Ejemplo
data.idUsuario String ID del usuario en la aplicación "user_12345"
data.token o data.tokenFirebase String Token de Firebase Cloud Messaging "dGh1c2lzYXRva2Vu..."
data.dispositivo String Información del dispositivo "Samsung Galaxy S21"
data.plataforma String Plataforma del dispositivo "android" o "ios"
data.appVersion String Versión de la aplicación "1.0.0"
estado String Estado del registro "activo" o "bloqueado"

⚠️ Nota sobre nomenclatura: Actualmente existe una inconsistencia en el código donde:

  • Al crear el registro se usa el campo data.token (definido en EnvironmentPushNotification.camposColeccionTokenFirebase)
  • Al consultar el registro se usa el campo data.tokenFirebase (en métodos anularToken() y _verificarTokenExistente())

Recomendación: Asegúrate de que tu colección MongoDB use data.tokenFirebase como nombre del campo, o actualiza el código para usar consistentemente data.token.

Ejemplo de Documento en MongoDB (Data)

{
  "_id": "507f1f77bcf86cd799439011",
  "idServer": "507f1f77bcf86cd799439011",
  "idMobile": "mobile_123",
  "estado": "activo",
  "creadoEl": "2025-01-15T10:30:00Z",
  "data": {
    "idUsuario": "user_12345",
    "tokenFirebase": "dGh1c2lzYXRva2VuZnJvbWZpcmViYXNl...",
    "dispositivo": "Samsung Galaxy S21",
    "plataforma": "android",
    "appVersion": "1.0.0"
  }
}

Nota: El ejemplo muestra el campo como tokenFirebase según la implementación actual en las consultas.

Índices Recomendados

Para optimizar el rendimiento, crea los siguientes índices en la colección:

// Índice único compuesto para evitar duplicados
db.tokenFirebase.createIndex(
  { "data.idUsuario": 1, "data.tokenFirebase": 1 },
  { unique: true }
)

// Índice para búsquedas por usuario
db.tokenFirebase.createIndex({ "data.idUsuario": 1 })

// Índice para búsquedas por token
db.tokenFirebase.createIndex({ "data.tokenFirebase": 1 })

// Índice para búsquedas por estado
db.tokenFirebase.createIndex({ "estado": 1 })

// Índice compuesto para consultas de tokens activos
db.tokenFirebase.createIndex(
  { "data.tokenFirebase": 1, "estado": 1 }
)

⚠️ IMPORTANTE: Orden de Campos

El orden de los campos en EnvironmentPushNotification.camposColeccionTokenFirebase es crítico:

static const camposColeccionTokenFirebase = [
  'idUsuario',      // 1. Primero
  'token',          // 2. Segundo
  'dispositivo',    // 3. Tercero
  'plataforma',     // 4. Cuarto
  'appVersion',     // 5. Quinto
];

Este orden debe coincidir con el orden en que se pasan los datos en el código. No alterar este orden.

Usage #

1. Inicialización en main.dart #

Importante: Ya no necesitas proporcionar coleccionFireBaseTokens porque ahora se usa la configuración de EnvironmentPushNotification.

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_firebase_push_notification_provider/flutter_firebase_push_notification_provider.dart';
import 'package:api_rest_flutter/api_rest.dart';
import 'package:api_rest_flutter/util/utils_data_api_rest.dart';

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

  // Inicializar Firebase primero
  await Firebase.initializeApp();

  // Crear instancias necesarias
  final apiRest = ApiRest(/* tu configuración */);
  final utilsDataApiRest = UtilsDataApiRest(/* tu configuración */);

  // Inicializar el manejador de notificaciones (usando el Singleton)
  try {
    final notificationProvider = FlutterFireBasePushNotificationProvider();
    await notificationProvider.initializeApp(
      apiRest: apiRest,
      utilsDataApiRest: utilsDataApiRest,
      idUsuario: 'USER_ID', // ID del usuario actual
      coleccionAuth: 'auth', // Colección de autenticación
    );

    print('✅ Notificaciones inicializadas correctamente');
  } catch (e) {
    print('❌ Error al inicializar notificaciones: $e');
  }

  runApp(MyApp());
}

2. Escuchar mensajes en tu widget #

import 'dart:async';
import 'package:flutter_firebase_push_notification_provider/flutter_firebase_push_notification_provider.dart';

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription<Map<String, dynamic>>? _subscription;
  late final FlutterFireBasePushNotificationProvider _notificationProvider;

  @override
  void initState() {
    super.initState();

    // Obtener la instancia del Singleton (siempre devuelve la misma instancia)
    _notificationProvider = FlutterFireBasePushNotificationProvider();

    // Verificar que esté inicializado antes de suscribirse
    if (_notificationProvider.isInitialized) {
      _suscribirseANotificaciones();
    } else {
      print('⚠️ El provider no está inicializado');
    }
  }

  void _suscribirseANotificaciones() {
    // Escuchar mensajes del stream
    _subscription = _notificationProvider.messagesStream.listen(
      (mensaje) {
        // Procesar notificación
        final idNotification = mensaje['data']?['IdPushNotification'];
        if (idNotification != null) {
          print('📩 ID Notificación: $idNotification');
          print('📩 Título: ${mensaje['notification']?['title'] ?? 'Sin título'}');
          print('📩 Mensaje: ${mensaje['notification']?['body'] ?? 'Sin mensaje'}');

          // Aquí puedes navegar a una pantalla específica o mostrar un dialog
          _procesarNotificacion(mensaje);
        }
      },
      onError: (error) {
        print('❌ Error en el stream de notificaciones: $error');
      },
    );
  }

  void _procesarNotificacion(Map<String, dynamic> mensaje) {
    // Implementa tu lógica de negocio aquí
    // Por ejemplo: navegación, actualización de UI, etc.
  }

  @override
  void dispose() {
    // Cancelar la suscripción para evitar memory leaks
    _subscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Notificaciones')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Estado: ${_notificationProvider.isInitialized ? "Inicializado" : "No inicializado"}'),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: _notificationProvider.hasPermission
                ? () => print('✅ Permisos otorgados')
                : () => print('⚠️ Sin permisos de notificación'),
              child: Text('Verificar Permisos'),
            ),
          ],
        ),
      ),
    );
  }
}

3. Obtener el Token FCM #

Existen dos formas de obtener el token FCM:

3.1. Acceder al token actual (Forma recomendada)

Puedes acceder directamente al token actual almacenado sin hacer una llamada asíncrona:

final notificationProvider = FlutterFireBasePushNotificationProvider();

// Acceso directo al token actual
final currentToken = notificationProvider.currentToken;

if (currentToken.isNotEmpty) {
  print('Token FCM actual: $currentToken');
  // Hacer algo con el token
} else {
  print('No hay token disponible');
}

3.2. Obtener el token manualmente (Llamada asíncrona)

Si necesitas obtener el token directamente desde Firebase (por ejemplo, para forzar una actualización):

final notificationProvider = FlutterFireBasePushNotificationProvider();
final token = await notificationProvider.getToken();

if (token.isNotEmpty) {
  print('Token FCM: $token');
  // Hacer algo con el token
} else {
  print('No se pudo obtener el token');
}

Nota: La propiedad currentToken es más eficiente ya que accede directamente al token almacenado sin necesidad de hacer una llamada asíncrona a Firebase.

4. Anular un Token FCM (Cerrar Sesión/Dispositivo) #

Cuando un usuario cierra sesión o necesitas desactivar las notificaciones push en un dispositivo específico, debes anular el token de Firebase. Este método:

  1. Elimina el token de Firebase Cloud Messaging en el dispositivo
  2. Marca el token como bloqueado en tu base de datos MongoDB (cambia el estado de activo a bloqueado)

Importante: El método anularToken() usa automáticamente el token actual almacenado (currentToken), por lo que NO necesitas pasar el token como parámetro.

¿Cuándo usar anularToken()?

  • 🔐 Cierre de sesión: Cuando el usuario hace logout
  • 📱 Cambio de dispositivo: Cuando el usuario se muda a otro dispositivo
  • 🔒 Seguridad: Cuando se detecta actividad sospechosa y necesitas revocar acceso
  • 🚫 Desactivar notificaciones: Cuando el usuario desea dejar de recibir notificaciones

Ejemplo de Uso

import 'package:flutter_firebase_push_notification_provider/flutter_firebase_push_notification_provider.dart';

Future<void> cerrarSesionUsuario() async {
  final notificationProvider = FlutterFireBasePushNotificationProvider();

  try {
    // Anular el token en Firebase y marcar como bloqueado en MongoDB
    // El método usa automáticamente el token actual almacenado
    await notificationProvider.anularToken(
      apiRest: apiRest,
      utilsDataApiRest: utilsDataApiRest,
    );

    print('✅ Token anulado correctamente');
    print('📱 El dispositivo ya no recibirá notificaciones');

    // Continuar con el proceso de cierre de sesión
    // Navigator.pushReplacement(...);

  } catch (e) {
    print('❌ Error al anular token: $e');
    // Manejar el error apropiadamente
    // Podrías mostrar un dialog o continuar con el logout de todas formas
  }
}

Ejemplo Completo en un Botón de Logout

ElevatedButton(
  onPressed: () async {
    // Mostrar diálogo de confirmación
    final confirmar = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Cerrar Sesión'),
        content: Text('¿Estás seguro que deseas cerrar sesión?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: Text('Cancelar'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: Text('Sí, cerrar sesión'),
          ),
        ],
      ),
    );

    if (confirmar == true) {
      // Mostrar indicador de carga
      showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) => Center(child: CircularProgressIndicator()),
      );

      try {
        final notificationProvider = FlutterFireBasePushNotificationProvider();

        // Anular token FCM (usa automáticamente el token actual almacenado)
        await notificationProvider.anularToken(
          apiRest: apiRest,
          utilsDataApiRest: utilsDataApiRest,
        );

        // Limpiar sesión local, SharedPreferences, etc.
        // await _limpiarSesionLocal();

        // Cerrar indicador de carga
        Navigator.pop(context);

        // Navegar a pantalla de login
        Navigator.pushReplacementNamed(context, '/login');

      } catch (e) {
        // Cerrar indicador de carga
        Navigator.pop(context);

        // Mostrar error
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Error al cerrar sesión: $e'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  },
  child: Text('Cerrar Sesión'),
)

¿Qué hace anularToken() internamente?

  1. Verifica el token actual: Comprueba que currentToken no esté vacío
  2. Elimina el token de FCM: Llama a FirebaseMessaging.instance.deleteToken() para eliminar el token del dispositivo
  3. Consulta MongoDB: Busca todos los registros en la colección tokenFirebase donde:
    • tokenFirebase = token actual almacenado (_currentToken)
    • estado = "activo"
  4. Actualiza el estado: Cambia el estado de todos los registros encontrados a "bloqueado"
  5. Logs detallados: Registra cada operación para debugging

Gestión de Estados

El sistema utiliza dos estados para los registros de tokens:

Estado Descripción Cuándo se usa
activo Token válido y en uso Cuando se crea o actualiza un token
bloqueado Token desactivado Cuando se anula un token con anularToken()

Esto permite mantener un historial de tokens sin eliminarlos físicamente de la base de datos, lo cual es útil para:

  • 📊 Auditoría: Saber qué dispositivos tenía el usuario
  • 🔍 Debugging: Identificar problemas de tokens duplicados
  • 📈 Análisis: Estadísticas de uso por dispositivo/plataforma

Manejo de Errores

try {
  await notificationProvider.anularToken(
    apiRest: apiRest,
    utilsDataApiRest: utilsDataApiRest,
  );
} on Exception catch (e) {
  // Error específico de la operación
  if (e.toString().contains('Error al leer registro')) {
    print('⚠️ No se pudo consultar la base de datos');
  } else if (e.toString().contains('Error al cambiar estado')) {
    print('⚠️ No se pudo actualizar el estado del token');
  } else {
    print('❌ Error desconocido: $e');
  }

  // Decidir si continuar con logout de todas formas
  // o mostrar error al usuario
} catch (e) {
  print('❌ Error inesperado: $e');
}

Notas Importantes

  • ⚠️ No reinicializar: Después de anular el token, NO intentes inicializar nuevamente sin reiniciar la app
  • 🔄 Nuevo login: Si el usuario inicia sesión nuevamente, la app debe reiniciarse o el token debe regenerarse
  • 📝 Logging: Todos los pasos se registran en logs de debug para facilitar troubleshooting
  • 🗄️ MongoDB: Los registros NO se eliminan, solo se marcan como bloqueado

5. Formato del mensaje #

Los mensajes recibidos en el stream tienen el siguiente formato:

{
  'senderId': String,
  'category': String?,
  'collapseKey': String?,
  'contentAvailable': bool,
  'data': Map<String, dynamic>, // Contiene IdPushNotification
  'from': String?,
  'messageId': String?,
  'messageType': String?,
  'mutableContent': bool,
  'notification': {
    'title': String?,
    'body': String?,
    'android': {...},
    'apple': {...}
  },
  'sentTime': int?, // millisecondsSinceEpoch
  'threadId': String?,
  'ttl': int?
}

Nota importante: Solo se procesarán mensajes que incluyan el campo IdPushNotification en el objeto data.

6. Verificar estado y permisos #

final notificationProvider = FlutterFireBasePushNotificationProvider();

// Verificar si está inicializado
if (notificationProvider.isInitialized) {
  print('✅ Provider inicializado');

  // Verificar permisos
  if (notificationProvider.hasPermission) {
    print('✅ Permisos de notificación otorgados');
  } else {
    print('⚠️ Sin permisos de notificación');
  }
} else {
  print('⚠️ Provider no inicializado');
}

// Verificar si está disposed
if (notificationProvider.isDisposed) {
  print('⚠️ Provider ya fue disposed, necesita reinicio de la app');
}

7. Actualización automática de tokens #

El paquete maneja automáticamente la actualización de tokens en tu servidor MongoDB cuando:

  • Se obtiene el token inicial
  • El token es actualizado por Firebase (token refresh)

Proceso de Actualización

  1. Verificación: El paquete primero verifica si el token ya existe en la base de datos
  2. Creación: Si no existe, crea un nuevo documento con toda la información
  3. Información Guardada:
    • idUsuario: ID del usuario actual
    • token: Token FCM
    • dispositivo: Marca y modelo del dispositivo (ej: "Samsung Galaxy S21")
    • plataforma: "android" o "ios"
    • appVersion: Versión de la app (ej: "1.0.0")

No necesitas llamar manualmente ningún método, todo se maneja automáticamente una vez inicializado.

8. Manejo del ciclo de vida de la app #

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);

    // SOLO disponer si la app se está cerrando completamente
    // NO llamar en dispose normal de widgets
    // final notificationProvider = FlutterFireBasePushNotificationProvider();
    // notificationProvider.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Mi App',
      home: HomeScreen(),
    );
  }
}

Manejo de Errores #

El paquete propaga errores apropiadamente, lo que permite manejarlos en tu código:

try {
  await notificationProvider.initializeApp(
    apiRest: apiRest,
    utilsDataApiRest: utilsDataApiRest,
    idUsuario: userId,
    coleccionAuth: 'auth',
  );
} on StateError catch (e) {
  // Error de estado (ya inicializado, disposed, Firebase no inicializado)
  print('Error de estado: $e');
} on FirebaseException catch (e) {
  // Error de Firebase
  print('Error de Firebase: ${e.message}');
} catch (e) {
  // Otros errores (red, API, esquema no encontrado, etc.)
  print('Error inesperado: $e');
}

Características Avanzadas #

Prevención de Memory Leaks #

El paquete gestiona automáticamente todas las subscriptions de Firebase:

  • FirebaseMessaging.onMessage
  • FirebaseMessaging.onMessageOpenedApp
  • FirebaseMessaging.onTokenRefresh

Todas se cancelan apropiadamente cuando se llama a dispose().

Protección contra Inicialización Múltiple #

El método initializeApp() puede llamarse múltiples veces de forma segura. Las llamadas adicionales serán ignoradas con un log de debug.

Manejo de Background Messages #

Los mensajes en background se procesan automáticamente incluso cuando la app está cerrada, gracias al handler @pragma('vm:entry-point') que se ejecuta en un isolate separado.

Verificación de Tokens Duplicados #

Antes de crear un nuevo registro en MongoDB, el paquete verifica si el token ya existe para evitar duplicados. Esto optimiza el uso de recursos y mantiene la base de datos limpia.

Additional information #

Notas técnicas #

  • El manejador de mensajes en background se ejecuta en un isolate separado
  • Los mensajes se filtran por el campo IdPushNotification en data
  • El logging solo aparece en modo debug (kDebugMode)
  • Todas las subscriptions se manejan internamente para prevenir memory leaks
  • Los tokens se actualizan automáticamente en el servidor con información del dispositivo
  • La consulta MongoDB utiliza escapado de caracteres para prevenir inyecciones
  • Los métodos _getDeviceInfo() y _getAppVersion() se ejecutan en paralelo para mejor performance
  • La colección tokenFirebase está configurada en EnvironmentPushNotification.coleccionTokenFirebase
  • El orden de campos en EnvironmentPushNotification.camposColeccionTokenFirebase es crítico y no debe alterarse
  • Los tokens se gestionan con estados activo y bloqueado para mantener historial
  • El método anularToken() elimina el token de FCM y marca registros como bloqueados en MongoDB
  • Las consultas de verificación filtran solo tokens con estado activo
  • Los tokens bloqueados se mantienen en la base de datos para auditoría y análisis

Solución de Problemas Comunes #

Error: "Firebase no está inicializado"

// Asegúrate de inicializar Firebase antes
await Firebase.initializeApp();

Error: "No se puede inicializar: la instancia ya fue disposed"

// Necesitas reiniciar la app. El singleton no puede ser reinicializado después de dispose()

Error: "No se pudo obtener esquema de la colección"

// Verifica que tu servidor tenga configurada la colección 'tokenFirebase'
// y que apiRest.obtieneEsquema() pueda acceder a ella

No recibo notificaciones en background

// Verifica que el mensaje incluya IdPushNotification en data
{
  "notification": {
    "title": "Título",
    "body": "Mensaje"
  },
  "data": {
    "IdPushNotification": "unique-id-123"  // ← Requerido
  }
}

Permisos de notificación denegados

// En iOS, los permisos solo se pueden solicitar una vez
// El usuario debe ir a Settings -> Tu App -> Notifications para habilitarlos
// En Android, se pueden solicitar múltiples veces

Token no se actualiza en el servidor

// Verifica:
// 1. Que la colección 'tokenFirebase' exista en MongoDB
// 2. Que el esquema sea correcto
// 3. Que apiRest tenga las credenciales correctas
// 4. Revisa los logs de debug para ver el error específico

Error: "Token ya existe en la base de datos"

// Esto es normal. El paquete verifica si el token existe antes de crear uno nuevo
// Este mensaje solo aparece en logs de debug y no es un error

Error al anular token

// Si anularToken() falla:
// 1. Verifica que haya un token actual almacenado (currentToken no vacío)
// 2. Verifica la conexión a MongoDB
// 3. Revisa los logs de debug para ver el error específico
// 4. Considera continuar con el logout incluso si falla (seguridad)

try {
  await notificationProvider.anularToken(
    apiRest: apiRest,
    utilsDataApiRest: utilsDataApiRest,
  );
} catch (e) {
  print('Error al anular token: $e');
  // Continuar con logout de todas formas
  _procederConLogout();
}

Token bloqueado sigue recibiendo notificaciones

// Los tokens bloqueados en MongoDB NO bloquean el envío desde Firebase
// Para detener las notificaciones completamente debes:
// 1. Usar anularToken() que elimina el token del dispositivo
// 2. NO enviar notificaciones a tokens marcados como 'bloqueado' desde tu servidor
// 3. Filtrar tokens activos al enviar notificaciones masivas

Ejemplo de Implementación Completa con BLoC #

Este ejemplo muestra cómo integrar el paquete en una aplicación Flutter usando el patrón BLoC para gestionar el estado de las notificaciones push.

1. Listener de Autenticación #

Primero, configuramos los listeners en relación al login para inicializar Firebase o anular el token cuando el usuario cierra sesión:

///[AUTH]
BlocListener<AuthBloc, AuthState>(
  listenWhen: (previous, current) => !current.isWorking,
  listener: (context, state) {
    if (state.error.isEmpty) {
      if (state.accion == AuthBloc.blocOnLoginAuth) {
        final estadoUsuario =
            FlutterUtilsProvider.determinaValorCampo([
              EnvironmentApiRest.estado,
            ], state.usuarioAuth);
        if (estadoUsuario == EstadoRegistro.bloqueado.valor) {
          ///TODO - Mostrar dialogo de cuenta bloqueada
          return;
        }
        final DataShpProvider shp = DataShpProvider();
        if (state.isLoggedIn &&
            !shp.keyLogin.startsWith(
              EnvironmentApiRest.userFlutter,
            )) {
          //
          context.read<ConfigAppBloc>().add(
            OnInitPushNotificaciones(
              usuarioAuth: state.usuarioAuth,
            ),
          );
        }
      }
      if (state.accion == AuthBloc.blocOnLogoutAuth) {
        context.read<ConfigAppBloc>().add(OnAnulaTokenFirebase());
      }
    }
  },
)

2. Eventos del BLoC #

Define los eventos que se dispararán para manejar las notificaciones push:

///Evento que inicializa las push notificaciones de Firebase
class OnInitPushNotificaciones extends ConfigAppEvent {
  final Map<String, dynamic> usuarioAuth;
  OnInitPushNotificaciones({required this.usuarioAuth});
  @override
  List<Object> get props => [usuarioAuth];
}

///Evento que se lanza cuando llega una nueva notificacion desde Firebase
class OnNuevaNotificacionFirebase extends ConfigAppEvent {
  final Map<String, dynamic> firebaseNotification;
  OnNuevaNotificacionFirebase({required this.firebaseNotification});
  @override
  List<Object> get props => [firebaseNotification];
}

///Evento que se lanza cuando se cierra sesion entonces se debe anular el token
///de firebase para que no lleguen mas notificaciones a este usuario
class OnAnulaTokenFirebase extends ConfigAppEvent {
  OnAnulaTokenFirebase();
  @override
  List<Object> get props => [];
}

3. Manejadores de Eventos en el BLoC #

Implementa los métodos que responden a estos eventos:

Future<void> _onInitPushNotificaciones(
  OnInitPushNotificaciones event,
  Emitter<ConfigAppState> emit,
) async {
  await _handleAction(onInitPushNotificaciones, emit, () async {
    if (notificationFireBaseProvider.isInitialized) {
      debugPrint(
        'Push notifications ya inicializadas, se omite reinicialización.',
      );
      return;
    }
    debugPrint('🔔 notificationFireBaseProvider.initializeApp Inicializa');
    await notificationFireBaseProvider.initializeApp(
      apiRest: apiRest,
      utilsDataApiRest: UtilsDataApiRest(apiRest: apiRest),
      idUsuario: event.usuarioAuth[EnvironmentApiRest.idServer],
      coleccionAuth: Environment.origenColeccion,
    );

    debugPrint('🔔 notificationFireBaseProvider.initializeApp');

    notificationFireBaseProvider.messagesStream.listen((mensaje) {
      debugPrint('🔔 Mensaje Firebase recibido: $mensaje');

      add(OnNuevaNotificacionFirebase(firebaseNotification: mensaje));
    });
  });
}

Future<void> _onNuevaNotificacionFirebase(
  OnNuevaNotificacionFirebase event,
  Emitter<ConfigAppState> emit,
) async {
  await _handleAction(onNuevaNotificacionFirebase, emit, () async {
    emit(state.copyWith(firebaseNotification: event.firebaseNotification));
  });
}

Future<void> _onAnulaTokenFirebase(
  OnAnulaTokenFirebase event,
  Emitter<ConfigAppState> emit,
) async {
  await _handleAction(onAnulaTokenFirebase, emit, () async {
    if (!notificationFireBaseProvider.isInitialized) {
      debugPrint(
        'Push notifications no están inicializadas, no hay nada que anular.',
      );
      return;
    }
    await notificationFireBaseProvider.anularToken(
      apiRest: apiRest,
      utilsDataApiRest: UtilsDataApiRest(apiRest: apiRest),
    );
  });
}

Flujo Completo #

  1. Login: Cuando el usuario inicia sesión exitosamente, se dispara OnInitPushNotificaciones
  2. Inicialización: El BLoC inicializa el provider de notificaciones con el ID del usuario
  3. Escucha de Mensajes: Se suscribe al stream de mensajes y dispara OnNuevaNotificacionFirebase cuando llega una notificación
  4. Actualización de Estado: El evento actualiza el estado del BLoC con la nueva notificación
  5. Logout: Cuando el usuario cierra sesión, se dispara OnAnulaTokenFirebase que elimina el token y lo marca como bloqueado en MongoDB

Ventajas de este Patrón #

  • Separación de Responsabilidades: La lógica de notificaciones está separada de la UI
  • Testeable: Los eventos y estados pueden ser fácilmente testeados
  • Mantenible: Cambios en la lógica de notificaciones no afectan otros componentes
  • Escalable: Fácil agregar nuevos eventos o estados relacionados con notificaciones
  • Reactivo: La UI se actualiza automáticamente cuando cambia el estado

Arquitectura Interna #

Flujo de Actualización de Token #

┌─────────────────┐
│ Inicialización  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Obtener Token   │
└────────┬────────┘
         │
         ▼
┌──────────────────────┐
│ Verificar Existencia │ ──► Si existe: Log y Retornar
└──────────┬───────────┘
           │
           ▼ No existe
┌──────────────────────┐
│ Obtener Info Device  │ ──┐
│ & App Version        │   │ (Paralelo)
└──────────┬───────────┘   │
           │               │
           ▼               ▼
┌──────────────────────────┐
│ Crear Documento MongoDB  │
└──────────┬───────────────┘
           │
           ▼
┌──────────────────────┐
│ Guardar en Servidor  │
└──────────────────────┘

Licencia #

Este proyecto está bajo la licencia especificada en el archivo LICENSE.

Versión #

v3.0.0 - Versión estable para web con:

  • ✅ Gestión automática de memoria
  • ✅ Prevención de memory leaks
  • ✅ Protección contra inicialización múltiple
  • ✅ Manejo robusto de errores
  • ✅ Actualización automática de tokens
  • ✅ Integración con información del dispositivo
  • ✅ Verificación de tokens existentes
  • ✅ Configuración centralizada en EnvironmentPushNotification
  • ✅ Escapado de consultas MongoDB para seguridad
  • ✅ Performance optimizado con llamadas paralelas
  • Método getToken() para obtener token manualmente
  • Método anularToken() para eliminar y bloquear tokens
  • Gestión de estados (activo/bloqueado) para tokens
  • Historial de tokens con estado bloqueado

Changelog #

Ver CHANGELOG.md para el historial completo de cambios.

0
likes
130
points
17
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for managing Firebase Cloud Messaging (FCM) push notifications for web applications. Handles permissions, token management, and message routing.

Documentation

API reference

License

MIT (license)

Dependencies

api_rest_flutter_web, device_info_plus, firebase_core, firebase_messaging, flutter, flutter_models_provider, flutter_utils_providers, package_info_plus

More

Packages that depend on flutter_firebase_push_notification_provider_web