I am trying to send a notification to an android device using firebase cloud messaging.
Below is sendNotification which is a cloud function I have deployed on firebase:
const sendNotification = (owner_uid: any, type: any) => {
return new Promise((resolve, reject) => {
admin.firestore().collection('users').doc(owner_uid).get().then((doc) => {
if (doc.exists && doc.data()?.token) {
if (type === 'new_comment') {
console.log('NEW COMMENT');
console.log('TOKEN: ' + doc.data()?.token);
admin.messaging().sendToDevice(doc.data()?.token, {
data: {
title: 'A new comment has been made on your post',
}
}).then((sent) => {
console.log("SENT COUNT " + sent.successCount);
console.log('SENT APPARENTLY')
resolve(sent);
});
}
}
});
});
}
And here is where I'm calling this function:
export const updateCommentsCount = functions.firestore.document('comments/{commentId}').onCreate(async (event) => {
const data = event.data();
const postId = data?.post;
const doc = await admin.firestore().collection('posts').doc(postId).get();
if (doc.exists) {
let commentsCount = doc.data()?.commentsCount || 0;
commentsCount++;
await admin.firestore().collection('posts').doc(postId).update({
'commentsCount': commentsCount
})
return sendNotification(doc.data()?.owner, 'new_comment');
} else {
return false;
}
})
However, I'm not receiving a notification on the android device.
And here are the cloud function logs when I leave a comment:
Can someone please tell me why is happening, & how it can be resolved? I can show further code if required.
I managed to find the solution.
In the notification sending method, sendToDevice, I updated the key "data", to "notification" and the notification is now being automatically sent & displayed on the original user's device.
Here is the updated
admin.messaging().sendToDevice(doc.data()?.token, {
notification: {
title: 'A new comment has been made on your post',
}
Related
I am very frustrated with this problem:(
I am developing an app for android and ios (using capacitor 3) and I am sending notifications to the app via firebase notifications. (capacitor packages: #capacitor-community/fcm and #capacitor/push-notifications).
It works for a while and after one day or a few days that the app is running in background or foreground (and not killed) it stops from working and the app doesn't get notifications(This has happened to me in android device.).
I am sending notifications using topics and i also tried to send the notification through firebase console, but it didn't work.
I am not sure if this means that the registration token has expired because I would think that the capacitor packages are suppose to handle it since they are not talking about this problem.
I did everything from the documentation of capacitor push notifications.
When I watch the logs I can see the next error: Failed to sync topics. Won't retry sync. INVALID_PARAMETERS.
My code in javascript:
import '#capacitor/core';
import { ActionPerformed, PushNotificationSchema, PushNotifications } from '#capacitor/push-notifications'
import { FCM } from '#capacitor-community/fcm';
import { getMessaging, getToken as firebaseGetToken, onMessage, deleteToken, isSupported } from "firebase/messaging";
import { myAxios } from './generic-functions/my-axios';
const platform = window.Capacitor && window.Capacitor.platform;
const topicIos = `${process.env.REACT_APP_TOPIC}_ios`;
const topicAnd = `${process.env.REACT_APP_TOPIC}_and`;
function isCapacitor(): boolean {
//check if we are in a capacitor platform
return window.Capacitor && (window.Capacitor.platform === "android" || window.Capacitor.platform === "ios")
}
export async function InitFCM(destination: string) {
if (!isCapacitor()) {
const isNtfSupported = await isSupported()
if (!isNtfSupported) return
// web notifications
Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
subscribeTo(destination);
} else {
// Show some error
}
});
const messaging = getMessaging();
onMessage(messaging, (payload) => {
let notification = payload.data;
const notificationOptions: NotificationOptions = {
badge: notification?.largeIco,
body: notification?.body,
icon: notification?.largeIcon
};
const title = notification?.title || "";
// show notification
navigator.serviceWorker
.getRegistrations()
.then((registration) => {
if (notification?.sound) {
const audio = new Audio(`/notifications/${notification?.sound}`)
audio.play()
}
registration[0].showNotification(title, notificationOptions);
});
})
return
}
try {
console.log('Initializing Push Notifications');
// Request permission to use push notifications
// iOS will prompt user and return if they granted permission or not
// Android will just grant without prompting
PushNotifications.requestPermissions().then(result => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
// PushNotifications.register();
subscribeTo(destination);
} else {
// Show some error
}
});
// Some issue with our setup and push will not work
PushNotifications.addListener('registrationError',
(error: any) => {
console.log('Error on registration: ' + JSON.stringify(error));
}
);
// Show us the notification payload if the app is open on our device
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotificationSchema) => {
console.log('Push received: ' + JSON.stringify(notification));
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: ActionPerformed) => {
console.log('Push action performed: ' + JSON.stringify(notification));
}
);
} catch (e) {
console.log('err in push notifications: ', e);
}
}
async function subscribeTo(destination: string) {
if (!isCapacitor()) {
//subscribe to web topic
const messaging = getMessaging();
firebaseGetToken(messaging, { vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY }).then(
async (token) => {
if (token) {
await myAxios.post("/api/notifications/subscribe-to-topic", { token, destination });
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
});
return
}
try {
await PushNotifications.register();
if (platform === "ios") {
//subscribe to ios topic
const resIos = await FCM.subscribeTo({ topic: `${topicIos}_${destination}` });
console.log(`subscribed to ios Topic ${JSON.stringify(resIos)}`);
}
if (platform === "android") {
//subscribe to android topic
const resAnd = await FCM.subscribeTo({ topic: `${topicAnd}_${destination}` });
console.log(`subscribed to android Topic ${JSON.stringify(resAnd)}`);
}
} catch (error) {
console.log(JSON.stringify(error));
}
}
export async function getToken() {
try {
/* const result = */ await FCM.getToken();
// console.log("TOKEN", result.token);
} catch (error) {
console.log(error);
}
}
export async function unsubscribeFrom(destination?: string) {
if (!isCapacitor()) {
const isNtfSupported = await isSupported()
if (!isNtfSupported || !destination) return
const messaging = getMessaging();
//unsubscribe from web topic
firebaseGetToken(messaging, { vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY }).then(
async (token) => {
if (token) {
await myAxios.post("/api/notifications/unsubscribe-from-topic", { token, destination });
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
});
return
}
try {
await PushNotifications.removeAllListeners();
if (destination) {
if (platform === "ios") {
//unsubscribe from ios topic
const resIos = await FCM.unsubscribeFrom({ topic: `${topicIos}_${destination}` });
console.log(`unsubscribed from ios topic ${resIos}`);
}
if (platform === "android") {
//unsubscribe from android topic
const resAndroid = await FCM.unsubscribeFrom({ topic: `${topicAnd}_${destination}` });
console.log(`unsubscribed from android topic ${topicAnd}_${destination}: ${resAndroid.message}`);
}
}
} catch (error) {
console.log(error)
}
if (platform === 'android') {
await FCM.deleteInstance();
}
}
Thank you all in advanced!
This is a common issue since Android 7.0. The problem occurs because you make use of data messages. This part of your code onMessage(messaging, (payload) => { tells me that you rely on that. This means that when a message is received, your apps code will handle the delivery even when in the background. It will create a notification to show it on the device and play a sound for example.
Power Management taken too far
Several device manufacturers have improved their power management too far. This results in the following problem: After a few days of inactivity, an app is completely killed by the Android OS. This means that the app is not able to handle incoming messages in the background anymore. Vendors have gone too far. But you can't do anything about that.
What to do?
To solve the problem, you should rely on notification messages. These are messages that are directly delivered to the Android OS, instead of your app. This means that messages do not need background handling of your app. On the server (sending) side it means you have to modify your current message and add notification info to the message that is sent.
The drawback
The drawback of notification messages is that you can't lay your hands on the data that is part of the notification. If you previously filled your app with data from each notification, with notification messages, you get the data only when your app is in the foreground or the notification is clicked. To get all data within your app, you need a server API solution or something else.
To overcome this you can add a NotificationListener to your app. I am not sure how to do this in Capacitor. A native example can be found here: https://github.com/Chagall/notification-listener-service-example. The NotificationListener can listen for notifications delivered to the Android device also in the background. With this solution you can be sure notifications are always delivered and the data is delivered in the background. But maybe, I don't know, this listener is killed too by power management. When you use the NotificationListener, you need a special permission, that must be set via device settings (see the mentioned example).
Conclusion
Change from data messages to notification messages. Provide a different way to get the data of your messages in your app. You can use the NotificationListener but I don't know if that is reliable. The most obvious solution is to introduce a server side API that provides the data to your app. In the new situation the notifications are reliable delivered to the app.
I have react native application that targets to both IOS and android. I integrated firebase notifications in my react native mobile application. I face a problem for android when the app is in background then no callback is hit but notification is received in tray from firebase.
I want to add logic for notifications count when app is in background so that when user opens the app he can see that there are 2 or something like new notifications. also want to update the badge count.
I know how to update badge counter or notifications counter as i did when the app is in background then there is call back onNotification that is fired. but i want to hit some code when new notification is received while application is in background.
The code for notifcations
async componentWillUnmount() {
await this.notificationListener();
await this.notificationOpenedListener();
await this.messageListener();
}
messageListener = async () => {
// When Notification is Recived and app in fore ground
this.notificationListener = firebase
.notifications()
.onNotification(async notification => {
debugger
console.log("on notification");
const { title, body } = notification;
const badgeCount = await firebase.notifications().getBadge();
debugger
firebase.notifications().setBadge(badgeCount+1);
await this.props.clearNotificationCount(badgeCount+1).then(res => {
debugger
BadgeAndroid.setBadge(this.props.notifications.Count)
console.log("BadgeCount is" + badgeCount);
});
});
this.notificationOpenedListener = firebase
.notifications()
.onNotificationOpened(async notificationOpen => {
debugger
console.log("on open listner");
const { title, body } = notificationOpen.notification;
const badgeCount = await firebase.notifications().getBadge();
debugger
this.props.setNotificationCount(badgeCount).then(res => {
// BadgeAndroid.setBadge(badgeCount)
});
firebase.notifications().setBadge(0);
});
const notificationOpen = await firebase
.notifications()
.getInitialNotification();
if (notificationOpen) {
console.log("Notification open");
console.log(notificationOpen.notification);
const { title, body } = notificationOpen.notification;
}
this.messageListener = firebase.messaging().onMessage(message => {
console.log("on message listner");
// firebase.notifications().setBadge(2);
console.log(JSON.stringify(message));
});
};
checkPermission = async () => {
const enabled = await firebase.messaging().hasPermission();
if (enabled) {
await this.getFcmToken();
} else {
await this.requestPermission();
}
};
getFcmToken = async () => {
const fcmToken = await firebase.messaging().getToken();
if (fcmToken) {
let SaveFirebaseTokenEndpoint = ApiEndPoint.SavefireBasetoken;
let myResponse = await DataAccess.PostSecured(SaveFirebaseTokenEndpoint, {
DeviceToken: fcmToken
});
console.log(fcmToken);
console.log(myResponse);
} else {
// this.showAlert('Failed', 'No token received');
}
};
requestPermission = async () => {
try {
await firebase.messaging().requestPermission();
// User has authorised
} catch (error) {
// User has rejected permissions
}
};
onNotificationOpened() never hits in background
On Android, you can implement a headless JS to listen for notifs when your app is in background. Note that this will only work on data-only Firebase Messages.
There is also a known issue on Android devices, specially the chinese devices with modified OS's that has an aggressive battery saver. You can read more here.
On iOS, you cannot show notifs when the app is on background/closed.
I'm deploying a mobile application (for Android and iOS) through which the admin can send alert to users registered to a specific topic. To do that I'm using Realtime Database to store alerts and cloud functions to send notifications to topic.
I've the following cloud function deployed:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNewAlertNotification = functions.database.ref('/alerts').onWrite(event => {
const getValuePromise = admin.database()
.ref('alerts')
.orderByKey()
.limitToLast(1)
.once('value');
return getValuePromise.then(snapshot => {
const { text, topics, author } = snapshotToArray(snapshot)[0];
const payload = {
data: {
title: 'Avviso',
body: text,
icon: 'ic_stat_notify',
sound: 'default',
color: '#F3E03B',
tag: 'alerts',
ticker: 'Nuovo avviso',
subtitle: 'Avvisi',
author: JSON.stringify(author)
}
};
const options = {
priority: 'high',
timeToLive: 60 * 60 * 24 * 2, // 48 hours
collapseKey: 'it.bmsoftware.caliup'
// contentAvailable: true
};
if (topics.length > 1) {
let condition = '';
topics.forEach((topic, index) => {
condition += `'${topic}' in topics`
if (index < topics.length - 1) {
condition += ' || '
}
});
console.log(`Sending alert to condition '${condition}' -> ${JSON.stringify(payload)}`);
return admin.messaging().sendToCondition(condition, payload, options);
} else if (topics.length === 1) {
let topic = topics[0];
console.log(`Sending alert to topic '${topic}' -> ${JSON.stringify(payload)}`);
return admin.messaging().sendToTopic(topic, payload, options);
} else {
console.log(`No topics found`);
}
});
});
const snapshotToArray = (snapshot) => {
let result = []
if (!snapshot || !snapshot.val())
return result
snapshot.forEach((childSnapshot) => {
let item = childSnapshot.val()
item.key = childSnapshot.key
result.push(item)
})
return result
}
When I insert a new message on the realtime database, the above function fetch that message correctly and in the log section (on the firebase console) I see the correct custom log and a log that says status 'ok'.
Despite this, no notification arrives on devices. If I test the same topic from firebase console directly it works fine so devices are properly registered.
Is there something wrong with the cloud function that I'm missing?
I believe that you should uncomment // contentAvailable: true if you are sending only data payload, at least for iOS. That way you'll be able to show and trigger the notification yourself on the app code. If you want the notification to pop up without having to process the data payload, you should pass a notification object on payload.
Notification is limited to these fields tho: https://firebase.google.com/docs/reference/admin/node/admin.messaging.NotificationMessagePayload
I need help, I've been searching for solutions all day but I can't fix my issue, the code below won't read the device tokens.
Below contains my db structure. I manage to receive the log: 'We have a new News for you.' When I added a new post but I received the log "There are no notification tokens to send to." Which means it cannot detect the device tokens even though there is already ones. What am I doing wrong?
{
"Newsv2" : {
"All" : {
"-Ktr7ZkuChCjsUIMb_4f" : {
"title" : "",
"type" : "",
}
},
"Usersv2" : {
"h0RzzpdO7nZVLpAR4fi7xRWUqsT2" : {
"device_token" : "",
"name" : "",
"user_no" : ""
}
},
}
/--News
--All
--name
--desc
/--Usersv2
--{userID}
--device_token
exports.sendNotif = functions.database.ref('/Newsv2/All/{newsID}').onWrite(event => {
const newsID = event.params.newsID;
const userID = event.params.userID;
if (!event.data.val()) {
return console.log('News! ', newsID);
}
console.log('We have a new News for you!',newsID);
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database().ref(`/Usersv2/${userid}/device_token`).once('value');
return Promise.all([getDeviceTokensPromise]).then(results => {
const tokensSnapshot = results[0];
//const follower = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
//console.log('Fetched follower profile', follower);
// Notification details.
const payload = {
notification: {
title: 'Test Message',
body: '',
icon: ''
}
};
// Listing all tokens.
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
To get the device token I store it in my firebase DB when a user registers or logs in.
private DatabaseReference mUserDatabase;
mUserDatabase = FirebaseDatabase.getInstance().getReference().child("Users/");
//and if the login/register is successful
mUserDatabase.child("device_token").setValue(deviceToken).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Intent intent = new Intent(application.getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_NEW_TASK);
application.startActivity(intent);
}
});
as for my firebase funciton:
const deviceToken = admin.database().ref(`/Users/${unique_id}/device_token`).once('value');
I am trying to make a cloud function that sends a push notification to a given user.
The user makes some changes and the data is added/updated under a node in firebase database (The node represents an user id). Here i want to trigger a function that sends a push notification to the user.
I have the following structure for the users in DB.
Users
- UID
- - email
- - token
- UID
- - email
- - token
Until now i have this function:
exports.sendNewTripNotification = functions.database.ref('/{uid}/shared_trips/').onWrite(event=>{
const uuid = event.params.uid;
console.log('User to send notification', uuid);
var ref = admin.database().ref('Users/{uuid}');
ref.on("value", function(snapshot){
console.log("Val = " + snapshot.val());
},
function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
When i get the callback, the snapshot.val() returns null. Any idea how to solve this? And maybe how to send the push notification afterwards?
I managed to make this work. Here is the code that sends a notification using Cloud Functions that worked for me.
exports.sendNewTripNotification = functions.database.ref('/{uid}/shared_trips/').onWrite(event=>{
const uuid = event.params.uid;
console.log('User to send notification', uuid);
var ref = admin.database().ref(`Users/${uuid}/token`);
return ref.once("value", function(snapshot){
const payload = {
notification: {
title: 'You have been invited to a trip.',
body: 'Tap here to check it out!'
}
};
admin.messaging().sendToDevice(snapshot.val(), payload)
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
})
Just answering the question from Jerin A Mathews...
Send message using Topics:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
//Now we're going to create a function that listens to when a 'Notifications' node changes and send a notificcation
//to all devices subscribed to a topic
exports.sendNotification = functions.database.ref("Notifications/{uid}")
.onWrite(event => {
//This will be the notification model that we push to firebase
var request = event.data.val();
var payload = {
data:{
username: request.username,
imageUrl: request.imageUrl,
email: request.email,
uid: request.uid,
text: request.text
}
};
//The topic variable can be anything from a username, to a uid
//I find this approach much better than using the refresh token
//as you can subscribe to someone's phone number, username, or some other unique identifier
//to communicate between
//Now let's move onto the code, but before that, let's push this to firebase
admin.messaging().sendToTopic(request.topic, payload)
.then((response) => {
console.log("Successfully sent message: ", response);
return true;
})
.catch((error) => {
console.log("Error sending message: ", error);
return false;
})
})
//And this is it for building notifications to multiple devices from or to one.
Return this function call.
return ref.on("value", function(snapshot){
console.log("Val = " + snapshot.val());
},
function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
This will keep the cloud function alive until the request is complete. Learn more about returning promises form the link give by Doug in the comment.
Send Notification for a Topic In Cloud function
Topics a basically groups you can send notification for the selected group
var topic = 'NOTIFICATION_TOPIC';
const payload = {
notification: {
title: 'Send through Topic',
body: 'Tap here to check it out!'
}
};
admin.messaging().sendToTopic(topic,payload);
You can register the device for any new or existing topic from mobile side
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotificationToTopic =
functions.firestore.document('Users/{uuid}').onWrite(async (event) => {
//let title = event.after.get('item_name');
//let content = event.after.get('cust_name');
var message = {
notification: {
title: "TGC - New Order Recieved",
body: "A New Order Recieved on TGC App",
},
topic: 'orders_comming',
};
let response = await admin.messaging().send(message);
console.log(response);
});
For sending notifications to a topic, The above code works well for me, if you have any doubt, let me know.