Suporte a notificações push
Acompanhe as interações do usuário com notificações push para medir campanhas de reengajamento e atribuir conversões com precisão, integrando o Firebase Cloud Messaging (FCM) com o Singular SDK.
Siga as diretrizes de implementação abaixo para garantir que os dados de notificação sejam passados corretamente para o Singular SDK para atribuição adequada.
Por que rastrear notificações push: As notificações push impulsionam o reengajamento, mas o rastreamento requer a integração correta. Singular garante que os usuários que interagem com as notificações sejam atribuídos corretamente, otimizando campanhas de marketing e estratégias de engajamento.
Guia de implementação
Configurar o Firebase Cloud Messaging
Instale os pacotes do Firebase e defina configurações específicas da plataforma para suporte a notificações push em seu aplicativo Flutter.
Instalar os pacotes do Firebase
Adicione as dependências do Firebase ao seu arquivo pubspec.yaml para obter a funcionalidade principal e o suporte a mensagens.
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.24.2
firebase_messaging: ^14.7.10
singular_flutter_sdk: ^1.8.0
Depois de adicionar as dependências, execute flutter pub get para instalar os pacotes.
Configuração do iOS
Registre seu aplicativo iOS no Firebase e configure os recursos de notificação por push no Xcode.
- Registre o aplicativo iOS: Crie um aplicativo iOS no seu projeto do Firebase Console
-
Adicionar arquivo de configuração: Baixe
GoogleService-Info.pliste adicione-o à pasta Xcode Runner - Habilitar recursos: Nas configurações do projeto Xcode, ative o recurso Notificações por push
- Habilitar modos de segundo plano: Habilite os Modos em segundo plano e marque Notificações remotas
Configuração do Android
Registre seu aplicativo Android no Firebase e adicione o arquivo de configuração ao seu projeto.
- Registar a aplicação Android: Crie um aplicativo Android no seu projeto do Firebase Console
-
Adicionar arquivo de configuração: Baixe
google-services.jsone coloque-o emandroid/app/ - Verificar dependências: Verifique se as dependências de mensagens do Firebase foram adicionadas e se as permissões foram concedidas em AndroidManifest.xml
Inicializar o Firebase no Flutter
Configure o Firebase para inicializar antes de executar seu aplicativo Flutter e configure o manipulador de mensagens em segundo plano para notificações recebidas quando o aplicativo não estiver em primeiro plano.
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 caminhos de link de envio
Defina os caminhos JSON onde os links de rastreamento Singular estão localizados na estrutura de carga útil da notificação por push.
Configure os caminhos de link de push passando matrizes de cadeias de caracteres que especificam o caminho principal para o link Singular em sua estrutura de dados de notificação. Cada caminho é uma matriz que representa a estrutura aninhada de chaves.
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);
Exemplos de configuração de caminho:
-
Chaves simples: Use
['sng_link']para chaves de nível superior no payload -
Chaves aninhadas: Use
['rootObj', 'nestedObj', 'key']para percorrer estruturas JSON aninhadas - Vários caminhos: Defina várias matrizes de caminho para verificar diferentes locais possíveis para links Singulares
Propriedade de configuração:
List<List<String>>? pushNotificationsLinkPaths
Para obter a documentação de configuração completa, consulte a referência pushNotificationsLinkPaths.
Manipulação específica da plataforma
Manipulação de notificações push no Flutter
Implemente ouvintes de mensagens do Firebase para capturar dados de notificação em estados de primeiro e segundo plano e, em seguida, passe os dados para Singular para rastreamento de atribuição.
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(),
);
}
}
Assinatura do método:
static void handlePushNotification(Map<String, dynamic> notificationData)
Para obter a documentação completa do método, consulte a referência handlePushNotification.
Configuração nativa do iOS
Aplicativo em estado terminado
Configure seu AppDelegate do iOS para passar opções de inicialização para o SDK Singular para rastreamento automático de push quando o aplicativo for aberto a partir de um estado finalizado.
Em AppDelegate.m ou AppDelegate.swift, passe as opções de lançamento para o SDK Singular:
Implementação Objective-C
// 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];
}
Implementação 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)
}
Tratamento automático: Quando os usuários tocam nas notificações push enquanto seu aplicativo não está em execução, o Singular captura automaticamente o payload da notificação durante a inicialização do aplicativo por meio das opções de inicialização.
Configuração nativa do Android
Aplicativo em segundo ou primeiro plano
Configure sua MainActivity do Android para passar intenções de notificação para o SDK Singular quando o aplicativo estiver em estados de segundo ou primeiro plano.
Em sua MainActivity, substitua onNewIntent para passar intents para o Singular:
Implementação 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);
}
}
Implementação Kotlin
// 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)
}
}
Aplicativo em estado terminado
Nenhuma configuração adicional é necessária para aplicativos Android em estado terminado. A camada de ponte do Flutter lida com esse cenário automaticamente quando os usuários tocam nas notificações.
Tratamento automático: Quando os utilizadores tocam nas notificações push enquanto a sua aplicação não está em execução, o Singular captura automaticamente os dados da notificação através da integração da bridge nativa.
Guia de Validação
Verificar carga útil na sessão inicial
Confirme que os links das notificações push são passados corretamente para o Singular inspecionando a chamada da API de início de sessão.
O SDK do Singular inclui a carga útil da notificação push no parâmetro singular_link na solicitação de sessão inicial quando os usuários tocam nas notificações.
Exemplo de solicitação de início de sessão:
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
Verificação alternativa: Use o Console do SDK Singular para verificar o rastreamento de notificações por push. Verifique o campo URL do Deeplink para confirmar que o link de rastreamento foi capturado corretamente.
Configuração avançada
Configuração de domínio ESP
Configure domínios externos se você envolver links do Singular dentro do Provedor de serviços de e-mail (ESP) ou outros domínios de terceiros.
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);
Propriedade de configuração:
List<String>? espDomains
Segurança Observação: Por padrão, somente domínios sng.linkpredefinidos na página Gerenciar links do Singular são permitidos. Configure domínios ESP explicitamente se estiver usando links agrupados.
Para obter a documentação de configuração completa, consulte a referência espDomains.
Roteamento dinâmico de deep link
Implemente vários destinos de deep link a partir de uma única notificação, configurando um link de rastreamento Singular com substituições de redirecionamento dinâmico.
Exemplo de caso de uso: Uma notificação de notícias de última hora com várias opções de ação
-
Ler as últimas notícias:
newsapp://article?id=12345 -
Tópicos em destaque:
newsapp://trending -
Desporto:
newsapp://sports
Em vez de criar vários links de rastreamento, use um link Singular e substitua os redirecionamentos dinamicamente com base na seleção do usuário. Consulte Substituição de redirecionamentos em links de rastreamento singulares para obter detalhes de implementação.
Exemplo de implementação completa
Implementação abrangente de notificação push com configuração do Firebase, configuração Singular e manipuladores específicos da plataforma para aplicativos Flutter.
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')),
);
}
}
Considerações importantes
Notas de implementação
-
Nenhum manipulador de retorno de chamada: Ao contrário de
singularLinksHandler, o recurso de notificação push não fornece retornos de chamada de carga. Implemente a sua própria lógica de ligação profunda para encaminhar os utilizadores para conteúdos específicos dentro da sua aplicação - Fluxo de atribuição: quando os usuários tocam nas notificações, o Singular recupera o payload e o inclui no evento de início de sessão acionado pela inicialização do SDK. O backend processa esses dados para atribuir o ponto de contato da notificação push e registrar o rastreamento de reengajamento
-
Restrições de domínio: Por padrão, somente domínios de link único (
sng.link) da página Gerenciar links são permitidos. Configure explicitamente os domínios ESP para links agrupados usandoespDomains - Diferenças de plataforma: o iOS requer a configuração do AppDelegate para o estado terminado, enquanto o Android lida com isso automaticamente através do módulo de ponte
- Testes: Ativar o registo SDK durante o desenvolvimento para verificar se os dados de notificação push são corretamente capturados e processados
Sucesso: Seguindo essas etapas, seu aplicativo agora rastreia as interações de notificação por push com o Singular, melhorando os insights de desempenho da campanha e garantindo uma atribuição precisa de reengajamento.