Reputation: 55
I'm developing a mobile application using Ionic + Capacitor with Firebase Cloud Messaging (FCM) for push notifications. Notifications work perfectly in the background and killed states on Android, but they fail to behave correctly in the foreground.
In Background/Killed State: Notifications arrive, are displayed, and tapping them triggers the correct event (pushNotificationActionPerformed) for deep linking into the app. In Foreground State: The pushNotificationReceived event triggers as expected. However, tapping the notification doesn’t trigger pushNotificationActionPerformed because the notification isn't displayed natively. I’ve implemented a temporary workaround to display notifications via Toast messages in the foreground, but this is not ideal since it doesn't replicate the native notification behavior.
Capacitor Configuration (capacitor.config.ts)
presentationOptions: ["badge", "sound", "alert"]
}
Frontend Code (NotificationService)
console.log('🚨 Notification received (Foreground):', notification);
const { title, body } = notification;
const data = notification.data || {};
await this.presentToastNotification(
title || 'New Notification',
body || 'You have a new message',
data?.conversacionId
);
console.log('✅ Notification shown as Toast in Foreground.');
});
Backend nodejs route notificacion
notificacionRuta.post('/crear-notificacion', async (req: Request, res: Response) => {
try {
const { receptorId, mensaje, titulo, conversacionId } = req.body;
if (!titulo || !mensaje || !receptorId || !conversacionId) {
return res.status(400).json({ error: 'El título, mensaje, receptorId y conversacionId son obligatorios.' });
}
// Crear y guardar la notificación
const notificacion = new Notificacion({
usuarioDestino: receptorId,
mensaje,
titulo,
fecha: new Date(),
tipo: 'mensaje',
leido: false,
});
const savedNotificacion = await notificacion.save();
console.log('✅ Notificación guardada:', savedNotificacion);
// Obtener los tokens del usuario
const receptor = await Usuario.findById(receptorId);
if (!receptor || !receptor.deviceTokens || receptor.deviceTokens.length === 0) {
return res.status(404).json({ error: 'Token del dispositivo no encontrado' });
}
// Enviar notificación push
await sendPushNotificationMultiple(receptor.deviceTokens, mensaje, titulo, receptorId, conversacionId);
res.status(201).json(savedNotificacion);
} catch (error) {
console.error('❌ Error al crear la notificación:', error);
logger.error('Error al crear notificación', { error: JSON.stringify(error, null, 2) });
res.status(500).json({ error: 'Error al crear notificación', details: error });
}
});
/**
* Función para enviar notificación push a múltiples dispositivos
*/
async function sendPushNotificationMultiple(
tokens: string[],
mensaje: string,
titulo: string,
receptorId: string,
conversacionId: string
) {
try {
if (!tokens || tokens.length === 0) {
throw new Error('No hay tokens de dispositivos válidos para enviar la notificación.');
}
const payload = {
notification: {
title: titulo,
body: mensaje,
},
data: {
title: titulo,
body: mensaje,
type: 'chat',
conversacionId: conversacionId,
receptorId: receptorId,
},
android: {
priority: 'high' as const,
notification: {
channelId: 'default',
},
},
apns: {
payload: {
aps: {
contentAvailable: true,
alert: {
title: titulo,
body: mensaje,
},
},
},
headers: {
'apns-priority': '10',
},
},
};
const response = await admin.messaging().sendEachForMulticast({
tokens,
notification: payload.notification,
data: payload.data,
android: payload.android,
apns: payload.apns,
});
console.log('✅ Notificaciones enviadas:', response);
const invalidTokens: string[] = [];
response.responses.forEach((resp, idx) => {
if (!resp.success) {
console.error(
`❌ Error en el token ${tokens[idx]}: ${resp.error?.message || 'Error desconocido'}`
);
if (resp.error?.code === 'messaging/registration-token-not-registered') {
invalidTokens.push(tokens[idx]);
}
}
});
if (invalidTokens.length > 0) {
await Usuario.updateOne(
{ _id: receptorId },
{ $pull: { deviceTokens: { $in: invalidTokens } } }
);
console.log('✅ Tokens inválidos eliminados:', invalidTokens);
}
} catch (error) {
console.error('❌ Error al enviar la notificación push múltiple:', error);
throw error;
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Permisos -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- Icono y canal de notificación predeterminados para Firebase -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="default" />
<!-- Main Activity -->
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:name="com.obdc.enigma.MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Firebase Messaging Service -->
<service
android:name="com.google.firebase.messaging.FirebaseMessagingService"
android:exported="true"
tools:replace="android:exported">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
<intent-filter>
<action android:name="FCM_PLUGIN_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<!-- FileProvider para compartir archivos -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
package com.obdc.enigma;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.getcapacitor.BridgeActivity;
import com.google.firebase.messaging.FirebaseMessaging;
import com.capacitorjs.plugins.pushnotifications.PushNotificationsPlugin;
public class MainActivity extends BridgeActivity {
private static final String TAG = "MainActivity";
private static final String CHANNEL_ID = "default";
private static final String CHANNEL_NAME = "Canal Predeterminado";
private static final String CHANNEL_DESCRIPTION = "Canal para notificaciones push predeterminadas";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 🔧 Crear el canal de notificaciones
createNotificationChannel();
// 🛡️ Inicializar el plugin de PushNotifications
initializePushNotificationsPlugin();
// 📲 Registrar Firebase Token
registerFirebaseToken();
}
/**
* 🔔 Crear el canal de notificaciones para dispositivos con Android 8+ (Oreo)
*/
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(CHANNEL_DESCRIPTION);
channel.enableLights(true);
channel.enableVibration(true);
channel.setLockscreenVisibility(android.app.Notification.VISIBILITY_PUBLIC);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
Log.d(TAG, "✅ Canal de notificaciones creado: " + CHANNEL_NAME);
} else {
Log.e(TAG, "❌ Error al obtener el NotificationManager");
}
}
}
/**
* 🛡️ Inicializar el plugin de PushNotifications
*/
private void initializePushNotificationsPlugin() {
try {
PushNotificationsPlugin pushPlugin = PushNotificationsPlugin.getPushNotificationsInstance();
if (pushPlugin != null) {
Log.d(TAG, "✅ PushNotificationsPlugin inicializado correctamente");
} else {
Log.e(TAG, "❌ Error al inicializar PushNotificationsPlugin");
}
} catch (Exception e) {
Log.e(TAG, "❌ Excepción al inicializar PushNotificationsPlugin: " + e.getMessage());
}
}
/**
* 📲 Registrar el dispositivo en Firebase y obtener el token de notificación
*/
private void registerFirebaseToken() {
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.e(TAG, "❌ Error al obtener el token de Firebase", task.getException());
return;
}
// 📱 Obtener el token del dispositivo
String token = task.getResult();
Log.d(TAG, "✅ Token de Firebase obtenido: " + token);
// Envía el token al backend
sendTokenToBackend(token);
});
}
/**
* 🚀 Enviar el token al backend para registrar el dispositivo
* @param token Token de Firebase
*/
private void sendTokenToBackend(String token) {
// Lógica para enviar el token al backend
Log.d(TAG, "🚀 Token enviado al backend: " + token);
}
}
Upvotes: 1
Views: 78
Reputation: 592
You can use the notification_foreground: "true"
property in the data payload to handle foreground notifications automatically without any manual trigger. Here's a reusable function to send notifications using the Firebase Admin SDK:
const admin = require("firebase-admin");
admin.initializeApp();
async function sendPushNotification(tokens, title, body) {
const multicastMessage = {
tokens: tokens,
notification: {
title: title,
body: body,
},
data: {
notificationType: "message",
notification_foreground: "true", // Ensure foreground notifications
},
android: {
notification: {
sound: "default", // Enable sound for Android
},
},
apns: {
payload: {
aps: {
sound: "default", // Enable sound for iOS
badge: 1, // Badge count for iOS
},
},
},
};
try {
const response = await admin.messaging().sendEachForMulticast(multicastMessage);
console.log("Successfully sent:", response.successCount);
console.log("Failed to send:", response.failureCount);
return response;
} catch (error) {
console.error("Error sending notifications:", error);
throw error;
}
}
const tokens = ["token1", "token2"]; // Replace with actual tokens
const title = "Hello!";
const body = "You have a new message.";
await sendPushNotification(tokens, title, body);
Upvotes: 1