SDK de Flutter - Soporte de notificaciones push

Documento

Soporte de notificaciones push

Realice un seguimiento de las interacciones de los usuarios con las notificaciones push para medir las campañas de reenganche y atribuir las conversiones con precisión mediante la integración de Firebase Cloud Messaging (FCM) con Singular SDK.

Siga las directrices de implementación que se indican a continuación para garantizar que los datos de las notificaciones se transmiten correctamente al SDK de Singular para una atribución adecuada.

Por qué realizar un seguimiento de las notificaciones push: Las notificaciones push impulsan el reenganche, pero el seguimiento requiere una integración correcta. Singular garantiza la correcta atribución de los usuarios que interactúan con las notificaciones, optimizando las campañas de marketing y las estrategias de compromiso.


Guía de implementación

Configurar Firebase Cloud Messaging

Instala los paquetes de Firebase y configura los ajustes específicos de la plataforma para la compatibilidad con notificaciones push en tu aplicación Flutter.

Instalar paquetes Firebase

Añada las dependencias de Firebase a su archivo pubspec.yaml para la funcionalidad básica y la compatibilidad con mensajería.

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.24.2
  firebase_messaging: ^14.7.10
  singular_flutter_sdk: ^1.8.0

Después de añadir las dependencias, ejecuta flutter pub get para instalar los paquetes.


Configuración de iOS

Registra tu aplicación iOS en Firebase y configura las capacidades de notificaciones push en Xcode.

  1. Registrar aplicación iOS: Crea una app iOS en tu proyecto Firebase Console.
  2. Add Configuration File: Descarga GoogleService-Info.plist y añádelo a la carpeta Xcode Runner
  3. Habilitar capacidades: En la configuración del proyecto Xcode, habilita la capacidad de notificaciones push
  4. Habilitar modos de fondo: Habilita Background Modes y marca Remote notifications

Configuración Android

Registre su aplicación Android en Firebase y añada el archivo de configuración a su proyecto.

  1. Registrar aplicación Android: Crea una app Android en tu proyecto de Consola Firebase
  2. Añadir archivo de configuración: Descarga google-services.json y colócalo en android/app/
  3. Verificar Dependencias: Asegúrate de que las dependencias de mensajería Firebase están añadidas y los permisos están concedidos en AndroidManifest.xml

Inicializar Firebase en Flutter

Configura Firebase para que se inicialice antes de ejecutar tu app Flutter y configura el gestor de mensajes en segundo plano para las notificaciones recibidas cuando la app no está en primer plano.

Dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

// Background message handler (must be top-level function)
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('Background message: ${message.messageId}');
  print('Data: ${message.data}');
}

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

  // Initialize Firebase
  await Firebase.initializeApp();

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

  runApp(MyApp());
}

Configurar rutas de enlaces push

Defina las rutas JSON en las que se encuentran los enlaces de seguimiento de Singular dentro de la estructura de carga útil de la notificación push.

Configure las rutas de enlaces push pasando matrices de cadenas que especifiquen la ruta clave al enlace Singular en su estructura de datos de notificación. Cada ruta es una matriz que representa la estructura anidada de claves.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';

SingularConfig config = SingularConfig(
  'YOUR_SDK_KEY',
  'YOUR_SDK_SECRET'
);

// Configure paths where Singular links are located in push payload
config.pushNotificationsLinkPaths = [
  ['sng_link'],                              // Top-level key
  ['path', 'to', 'url'],                     // Nested path
  ['rootObj', 'nestedObj', 'singularLink']   // Deep nested path
];

Singular.start(config);

Ejemplos de configuración de rutas:

  • Claves simples: Utilice ['sng_link'] para las claves de nivel superior de la carga útil.
  • Claves anidadas: Utilice ['rootObj', 'nestedObj', 'key'] para recorrer estructuras JSON anidadas.
  • Varias rutas: Defina varias matrices de rutas para comprobar las distintas ubicaciones posibles de los enlaces singulares.

Propiedad de configuración:

List<List<String>>? pushNotificationsLinkPaths

Para obtener documentación completa sobre la configuración, consulte la referencia pushNotificationsLinkPaths.


Gestión específica de la plataforma

Gestión de notificaciones push en Flutter

Implemente escuchadores de mensajes Firebase para capturar datos de notificación en estados de primer y segundo plano y, a continuación, pase los datos a Singular para el seguimiento de la atribución.

Dart
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:singular_flutter_sdk/singular.dart';

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

class _MyAppState extends State<MyApp> {
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

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

  void setupPushNotifications() async {
    // Request permission for iOS
    NotificationSettings settings = await _firebaseMessaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

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

    // Handle foreground notifications
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Foreground message received: ${message.messageId}');
      handleForegroundNotification(message);
    });

    // Handle notifications that opened the app from background
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('Notification opened app from background: ${message.messageId}');
      handleBackgroundNotification(message);
    });

    // Check for notification that launched the app (terminated state)
    RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
    if (initialMessage != null) {
      print('App launched from notification: ${initialMessage.messageId}');
      handleTerminatedNotification(initialMessage);
    }
  }

  void handleForegroundNotification(RemoteMessage message) {
    String title = message.notification?.title ?? '';
    String body = message.notification?.body ?? '';
    Map<String, dynamic> data = message.data;

    print('Title: $title');
    print('Body: $body');
    print('Data: $data');

    // Pass notification data to Singular
    if (data.isNotEmpty) {
      Singular.handlePushNotification(data);
    }

    // Display local notification or custom UI
    displayLocalNotification(title, body, data);
  }

  void handleBackgroundNotification(RemoteMessage message) {
    print('Processing background notification: ${message.messageId}');

    // Pass notification data to Singular
    if (message.data.isNotEmpty) {
      Singular.handlePushNotification(message.data);
    }

    // Navigate to specific screen based on notification data
    navigateFromNotification(message.data);
  }

  void handleTerminatedNotification(RemoteMessage message) {
    print('Processing terminated state notification: ${message.messageId}');

    // Pass notification data to Singular
    if (message.data.isNotEmpty) {
      Singular.handlePushNotification(message.data);
    }

    // Navigate to specific screen
    navigateFromNotification(message.data);
  }

  void displayLocalNotification(
    String title, 
    String body, 
    Map<String, dynamic> data
  ) {
    // Your notification display logic
    print('Displaying notification: $title - $body');
  }

  void navigateFromNotification(Map<String, dynamic> data) {
    // Your navigation logic based on notification data
    final route = data['route'];
    print('Navigating to: $route');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

Firma del método:

static void handlePushNotification(Map<String, dynamic> notificationData)

Para obtener la documentación completa del método, consulte la referencia handlePushNotification.


Configuración nativa de iOS

Aplicación en estado finalizado

Configure su AppDelegate de iOS para pasar opciones de lanzamiento al SDK de Singular para el seguimiento push automático cuando la aplicación se abre desde un estado finalizado.

En AppDelegate.m o AppDelegate.swift, pase las opciones de lanzamiento a Singular SDK:

Implementación Objective-C

AppDelegate.m
// Import at the top of the file
#import "SingularAppDelegate.h"

- (BOOL)application:(UIApplication *)application 
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];

    // Pass launch options to Singular for push tracking
    [SingularAppDelegate shared].launchOptions = launchOptions;

    return [super application:application 
        didFinishLaunchingWithOptions:launchOptions];
}

Implementación Swift

AppDelegate.swift
import singular_flutter_sdk

override func application(_ application: UIApplication, 
    didFinishLaunchingWithOptions 
    launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    // Pass launch options to Singular for push tracking
    if let singularAppDelegate = SingularAppDelegate.shared() {
        singularAppDelegate.launchOptions = launchOptions
    }

    return super.application(application, 
        didFinishLaunchingWithOptions: launchOptions)
}

Manejo automático: Cuando los usuarios tocan las notificaciones push mientras tu app no se está ejecutando, Singular captura automáticamente la carga útil de la notificación durante el lanzamiento de la app a través de las opciones de lanzamiento.


Configuración nativa de Android

Aplicación en segundo o primer plano

Configure su Android MainActivity para pasar notificaciones al SDK de Singular cuando la aplicación esté en segundo o primer plano.

En su MainActivity, anule onNewIntent para pasar intents a Singular:

Implementación Java

MainActivity.java
// Add imports at the top
import android.content.Intent;
import com.singular.flutter_sdk.SingularBridge;

// Add to MainActivity class
@Override
public void onNewIntent(Intent intent) {
    if(intent.getData() != null) {
        setIntent(intent);
        super.onNewIntent(intent);

        // Pass intent to Singular for push tracking
        SingularBridge.onNewIntent(intent);
    }
}

Implementación Kotlin

MainActivity.kt
// Add imports at the top
import android.content.Intent
import com.singular.flutter_sdk.SingularBridge

// Add to MainActivity class
override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    if (intent.data != null) {
        setIntent(intent)

        // Pass intent to Singular for push tracking
        SingularBridge.onNewIntent(intent)
    }
}

Aplicación en estado finalizado

No se requiere ninguna configuración adicional para las aplicaciones Android en estado finalizado. La capa puente de Flutter gestiona este escenario automáticamente cuando los usuarios tocan las notificaciones.

Gestión automática: Cuando los usuarios tocan las notificaciones push mientras su aplicación no se está ejecutando, Singular captura automáticamente los datos de notificación a través de la integración nativa del puente.


Guía de validación

Verificación de la carga útil en la sesión de inicio

Confirme que los enlaces de notificaciones push se pasan correctamente a Singular inspeccionando la llamada a la API de inicio de sesión.

Singular SDK incluye la carga útil de la notificación push en el parámetro singular_link en la solicitud de inicio de sesión cuando los usuarios tocan las notificaciones.

Ejemplo de solicitud de inicio de sesión:

https://sdk-api-v1.singular.net/api/v1/start?
a=<SDK-Key>
&singular_link=https://singularassist2.sng.link/C4nw9/r1m0?_dl=singular%3A%2F%2Ftest&_smtype=3
&i=net.singular.sampleapp
&s=1740905574084
&sdk=Singular/Flutter-v1.8.0

Verificación alternativa: Utilice la consola de Singular SDK para verificar el seguimiento de las notificaciones push. Compruebe el campo Deeplink URL para confirmar que el enlace de seguimiento se ha capturado correctamente.


Configuración avanzada

Configuración de dominios ESP

Configure dominios externos si envuelve enlaces de Singular dentro de dominios de proveedores de servicios de correo electrónico (ESP) u otros dominios de terceros.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';

// Configure ESP domains for wrapped Singular links
SingularConfig config = SingularConfig(
  'YOUR_SDK_KEY',
  'YOUR_SDK_SECRET'
);

config.espDomains = ['sl.esp.link', 'custom.domain.com'];

Singular.start(config);

Propiedad de configuración:

List<String>? espDomains

Seguridad Nota: de forma predeterminada, sólo se permiten los dominios sng.linkpredefinidos en la página Administrar enlaces de Singular. Configure los dominios ESP explícitamente si utiliza enlaces envueltos.

Para obtener documentación completa sobre la configuración, consulte la referencia espDomains.


Enrutamiento dinámico de enlaces profundos

Implemente múltiples destinos de enlaces profundos desde una única notificación configurando un enlace de seguimiento Singular con redireccionamientos dinámicos.

Ejemplo de uso: Una notificación de noticias de última hora con múltiples opciones de acción

  • Leer últimas noticias: newsapp://article?id=12345
  • Trending Topics: newsapp://trending
  • Deportes: newsapp://sports

En lugar de crear varios enlaces de seguimiento, utilice un enlace Singular y anule las redirecciones dinámicamente en función de la selección del usuario. Consulte Anulación de redireccionamientos en enlaces de seguimiento singulares para obtener detalles sobre la implementación.


Ejemplo de implementación completa

Implementación completa de notificaciones push con configuración de Firebase, configuración de Singular y controladores específicos de la plataforma para aplicaciones Flutter.

Dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';

// Background message handler (top-level function)
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('Background message: ${message.messageId}');

  // Singular handles background notifications automatically
  if (message.data.isNotEmpty) {
    print('Background notification data: ${message.data}');
  }
}

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

  // Initialize Firebase
  await Firebase.initializeApp();

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

  runApp(MyApp());
}

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

class _MyAppState extends State<MyApp> {
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

  @override
  void initState() {
    super.initState();
    initializeSingular();
    setupPushNotifications();
  }

  void initializeSingular() {
    // Configure Singular SDK
    SingularConfig config = SingularConfig(
      'YOUR_SDK_KEY',
      'YOUR_SDK_SECRET'
    );

    // Configure push link paths
    config.pushNotificationsLinkPaths = [
      ['sng_link'],
      ['data', 'url'],
      ['rootObj', 'nestedObj', 'singularLink']
    ];

    // Configure ESP domains if needed
    config.espDomains = ['sl.esp.link'];

    // Enable logging for debugging
    config.enableLogging = true;

    // Initialize SDK
    Singular.start(config);
  }

  void setupPushNotifications() async {
    // Request permission (iOS)
    NotificationSettings settings = await _firebaseMessaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      print('User granted permission');
    }

    // Get FCM token
    String? token = await _firebaseMessaging.getToken();
    print('FCM Token: $token');

    // Handle foreground notifications
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Foreground notification: ${message.messageId}');
      handleForegroundNotification(message);
    });

    // Handle notification that opened app from background
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('Notification opened app: ${message.messageId}');
      handleBackgroundNotification(message);
    });

    // Check for notification that launched the app
    RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
    if (initialMessage != null) {
      print('App launched from notification: ${initialMessage.messageId}');
      handleTerminatedNotification(initialMessage);
    }
  }

  void handleForegroundNotification(RemoteMessage message) {
    final title = message.notification?.title ?? '';
    final body = message.notification?.body ?? '';
    final data = message.data;

    print('Foreground - Title: $title, Body: $body');

    // Pass notification data to Singular
    if (data.isNotEmpty) {
      Singular.handlePushNotification(data);
    }

    // Display notification to user
    showNotificationDialog(title, body, data);
  }

  void handleBackgroundNotification(RemoteMessage message) {
    print('Processing background notification');

    // Pass notification data to Singular
    if (message.data.isNotEmpty) {
      Singular.handlePushNotification(message.data);
    }

    // Navigate based on notification data
    navigateFromNotification(message.data);
  }

  void handleTerminatedNotification(RemoteMessage message) {
    print('Processing terminated state notification');

    // Pass notification data to Singular
    if (message.data.isNotEmpty) {
      Singular.handlePushNotification(message.data);
    }

    // Navigate based on notification data
    navigateFromNotification(message.data);
  }

  void showNotificationDialog(
    String title, 
    String body, 
    Map<String, dynamic> data
  ) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: Text(body),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
              navigateFromNotification(data);
            },
            child: Text('Open'),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: Text('Dismiss'),
          ),
        ],
      ),
    );
  }

  void navigateFromNotification(Map<String, dynamic> data) {
    // Parse notification data for routing
    final route = data['route'];
    final productId = data['product_id'];

    print('Navigating to: $route');

    // Navigate to appropriate screen
    if (route == 'product' && productId != null) {
      navigatorKey.currentState?.pushNamed('/product/$productId');
    } else if (route == 'promo') {
      navigatorKey.currentState?.pushNamed('/promo');
    } else {
      navigatorKey.currentState?.pushNamed('/');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      navigatorKey: navigatorKey,
      initialRoute: '/',
      routes: {
        '/': (context) => HomeScreen(),
        '/product': (context) => ProductScreen(),
        '/promo': (context) => PromoScreen(),
      },
    );
  }
}

// Placeholder screens
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(child: Text('Home Screen')),
    );
  }
}

class ProductScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Product')),
      body: Center(child: Text('Product Screen')),
    );
  }
}

class PromoScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Promo')),
      body: Center(child: Text('Promo Screen')),
    );
  }
}

Consideraciones importantes

Notas de implementación

  • No hay controlador de devolución de llamada: A diferencia de singularLinksHandler, la función de notificaciones push no proporciona callbacks de carga útil. Implemente su propia lógica de enlace profundo para dirigir a los usuarios a contenidos específicos dentro de su aplicación.
  • Flujo de atribución: Cuando los usuarios tocan las notificaciones, Singular recupera la carga útil y la incluye en el evento de inicio de sesión activado por la inicialización del SDK. El backend procesa estos datos para atribuir el punto de contacto de la notificación push y registrar el seguimiento del reenganche.
  • Restricciones de dominio: Por defecto, sólo se permiten dominios de enlace singulares (sng.link) de la página Gestionar enlaces. Configure dominios ESP explícitamente para enlaces envueltos utilizando espDomains
  • Diferencias entre plataformas: iOS requiere la configuración de AppDelegate para el estado finalizado, mientras que Android lo gestiona automáticamente a través del módulo puente.
  • Pruebas: Habilite el registro del SDK durante el desarrollo para verificar que los datos de las notificaciones push se capturan y procesan correctamente.

Éxito: Siguiendo estos pasos, su aplicación ahora realiza un seguimiento de las interacciones de notificaciones push con Singular, lo que mejora la información sobre el rendimiento de la campaña y garantiza una atribución precisa del reenganche.