Push notifications
Registrar device tokens, manejar notificaciones y deeplinks.
Configuración con PushConfig
Configura los callbacks de push en SouthGamesSDK.init():
await SouthGamesSDK.init(
apiKey: 'sg_live_xxx',
orgId: 'mi-empresa',
pushConfig: PushConfig(
// Pedir permisos al inicializar (default: true)
requestPermissionOnInit: true,
// Mostrar notificaciones en foreground (default: true)
showForegroundNotifications: true,
// Callback cuando el usuario toca una notificación
onNotificationTap: (message) {
print('Tap: ${message.notification?.title}');
},
// Callback en foreground
onForegroundMessage: (message) {
print('Recibida: ${message.notification?.title}');
},
// Callback automático para deeplinks (ver abajo)
onDeeplinkReceived: (deeplink, message) {
navigatorKey.currentState?.pushNamed(deeplink);
},
),
);
El SDK obtiene el token FCM y lo registra automáticamente al llamar identify().
Deeplinks
Las notificaciones push pueden incluir un deeplink que se entrega al tocar la notificación. El deeplink se configura desde el dashboard al crear el push, y el SDK no lo abre ni lo interpreta: lo entrega parseado para que lo enrutes con tu propio router o sistema de configuración.
Handler unificado (recomendado, v0.8.0+)
onDeeplink es el punto único por el que pasan todos los deeplinks: los de pushes tocados y las acciones de CTAs/botones de notificaciones in-app. Recibe un SgDeeplink con la URI parseada y helpers de ruteo:
await SouthGamesSDK.init(
apiKey: '...',
orgId: '...',
onDeeplink: (link) {
// link.raw -> "myapp://promo/verano?code=ABC"
// link.pathSegments -> ["promo", "verano"]
// link.params -> {"code": "ABC"}
// link.source -> SgDeeplinkSource.push | SgDeeplinkSource.inApp
final promo = link.match('promo/:slug');
if (promo != null) {
context.go('/promos/${promo['slug']}'); // tu router, tus rutas
return;
}
if (link.match('config/:key') != null) {
applySetting(link.pathSegments[1], link.params); // acciones de config
}
},
);
match('patron/:param') compara contra los segmentos de ruta (para schemes propios como myapp://promo/verano el host cuenta como primer segmento) y devuelve los parámetros capturados, o null si no calza.
Cold start: deeplinks que llegan antes que tu router
Si el usuario toca el push con la app cerrada, el deeplink llega durante init() — antes de que exista tu navigator. El SDK lo maneja así:
- Con
init(onDeeplink: ...), la entrega ocurre después del primer frame, cuando tu navigator/router ya existe. No tienes que hacer nada. - Si registras el handler más tarde (setter), los deeplinks acumulados quedan pendientes y se entregan al registrarlo.
// Opción A (recomendada): pasar onDeeplink a init() — cold start cubierto
await SouthGamesSDK.init(apiKey: ..., orgId: ..., onDeeplink: handler);
// Opción B: registrar cuando tu router esté listo (entrega inmediata + pendientes)
SouthGamesSDK.instance.onDeeplink = (link) => router.go(link.raw);
// Opción C: consultar explícitamente al montar la app
final pending = SouthGamesSDK.instance.consumePendingDeeplink();
if (pending != null) router.go(pending.raw);
Push con notificación in-app vinculada
Si el envío se creó con la opción del dashboard "Mostrar también como notificación in-app", el push llega con data.inAppId. Al tocarlo, el SDK busca esa in-app y la muestra automáticamente con el overlay (aunque el usuario la hubiera descartado antes — es una apertura explícita). Para manejarla tú mismo:
PushConfig(autoOpenLinkedInApp: false)
// y luego, donde prefieras:
final inAppId = SouthGamesPush.getInAppId(message);
if (inAppId != null) {
await SouthGamesSDK.instance.getInAppNotificationById(inAppId);
}
Callback legacy
PushConfig.onDeeplinkReceived sigue funcionando igual que siempre (se ejecuta después de onNotificationTap, solo si hay deeplink). Ten en cuenta que este callback no tiene buffer de cold start — para eso usa onDeeplink:
PushConfig(
onDeeplinkReceived: (deeplink, message) {
navigatorKey.currentState?.pushNamed(deeplink);
},
)
Manejo manual
También puedes extraer el deeplink manualmente desde cualquier RemoteMessage:
import 'package:southgames_flutter/southgames_flutter.dart';
final deeplink = SouthGamesPush.getDeeplink(message);
if (deeplink != null) {
// Navegar a la pantalla correspondiente
}
Payload
El deeplink se envía como data.deeplink en el payload push (y data.inAppId cuando hay in-app vinculada):
{
"notification": {
"title": "¡Oferta especial!",
"body": "20% de descuento en tu próxima compra"
},
"data": {
"sendId": "abc123",
"deeplink": "myapp://promo/verano",
"inAppId": "notif456"
}
}
Imágenes en push (iOS)
Un push enviado desde el dashboard con imagen la muestra al expandir la notificación. En Android funciona sin configuración: FCM descarga y renderiza notification.image por su cuenta.
En iOS el sistema nunca descarga imágenes remotas por sí mismo. El servidor marca el push como mutable-content: 1 y envía la URL en el payload, pero la app necesita una Notification Service Extension que descargue la imagen y la adjunte antes de que iOS muestre la notificación. Sin esa extensión la push llega solo con título y cuerpo.
1. Crear la extensión en Xcode
Abre ios/Runner.xcworkspace y ve a File → New → Target → Notification Service Extension. Ponle un nombre (por ejemplo NotificationService), con el mismo Team y un bundle id bajo el de la app (com.tuapp.NotificationService). Xcode genera un archivo NotificationService.swift.
Asegúrate de que el deployment target de la extensión no sea mayor al de la app.
2. Reemplazar NotificationService.swift
La extensión no depende de Firebase ni del SDK, así que basta con este archivo:
import UserNotifications
final class NotificationService: UNNotificationServiceExtension {
private var contentHandler: ((UNNotificationContent) -> Void)?
private var bestAttempt: UNMutableNotificationContent?
private var task: URLSessionDownloadTask?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
bestAttempt = request.content.mutableCopy() as? UNMutableNotificationContent
// FCM entrega la URL en fcm_options.image; el envío directo por APNs en imageUrl.
let info = request.content.userInfo
let raw = (info["fcm_options"] as? [AnyHashable: Any])?["image"] as? String
?? info["imageUrl"] as? String
guard let raw, let url = URL(string: raw) else { return deliver() }
task = URLSession.shared.downloadTask(with: url) { [weak self] tmp, response, _ in
guard let self else { return }
if let tmp {
let ext = (response?.mimeType == "image/png") ? "png" : "jpg"
let dest = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString).appendingPathExtension(ext)
try? FileManager.default.moveItem(at: tmp, to: dest)
if let att = try? UNNotificationAttachment(identifier: "image", url: dest) {
self.bestAttempt?.attachments = [att]
}
}
self.deliver()
}
task?.resume()
}
override func serviceExtensionTimeWillExpire() {
task?.cancel()
deliver()
}
private func deliver() {
guard let handler = contentHandler, let content = bestAttempt else { return }
contentHandler = nil
handler(content)
}
}
Si tu app es nativa (Swift) y usas el paquete SouthGamesSDK, puedes en cambio enlazar el producto SouthGamesNotificationExtension a la extensión y subclasear SouthGamesNotificationService, que hace exactamente esto.
3. Verificar
- La URL de la imagen debe ser
https(App Transport Security bloqueahttp). - Formatos soportados: PNG y JPG (el dashboard ya convierte lo que subes).
- Prueba con la app en background: al expandir el push (mantener presionado o deslizar) aparece la imagen. En el simulador las extensiones no siempre corren; usa un dispositivo real.
Background handler
Para rastrear notificaciones recibidas en background, registra el handler en main.dart:
@pragma('vm:entry-point')
Future<void> _firebaseBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
await SouthGamesSDK.handleBackgroundMessage(message);
}
void main() {
FirebaseMessaging.onBackgroundMessage(_firebaseBackgroundHandler);
// ...
}
Registro manual de token
Si no usas Firebase Messaging o quieres registrar tokens de otro proveedor:
await SouthGamesSDK.push.registerDeviceToken(
externalId: 'user_123',
token: 'fcm_or_apns_token',
);
Proveedores compatibles
El SDK es agnóstico al proveedor de push. Puedes usar cualquiera:
- Firebase Cloud Messaging (FCM)
- Apple Push Notification service (APNs)
- OneSignal
- Cualquier otro que te dé un device token
Logout
Al hacer logout, el SDK desregistra el token automáticamente:
await SouthGamesSDK.logout();
// El token se elimina del servidor y se borra localmente