Versions
#react-native-firebase/app: 6.3.4
#react-native-firebase/messaging: 6.3.4
react: 16.9.0
react-native: 0.61.4
Inside my App.js file, I'm registering for notifications like this
...
import messaging from '#react-native-firebase/messaging';
const App: () => React$Node = () => {
const registerForNotifications = async () => {
const granted = await messaging().requestPermission();
if (granted) {
console.log('User granted messaging permissions!');
} else {
console.log('User declined messaging permissions :(');
}
await messaging().registerForRemoteNotifications();
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('FCM Message Data:', remoteMessage.data);
});
const token = await messaging().getToken();
console.log({token});
return unsubscribe;
};
useEffect(() => {
SplashScreen.hide();
registerForNotifications();
}, []);
...
}
and it shows me that user granted messaging permissions, and it seems to register correctly for remote notifications, and finally I get the token, which seems to be ok and connected.
In firebase messaging console, I send a new notification and in possible targets it seems to be my user (Inside Cloud messaging menu)
So I do not see any error, but for some reason I sent multiple notifications, and I don't receive them, it doesn't matter if the app is open or close. I don't know what to do because I cannot see any error.
Related
The expo push notifications through Firebase configuration was working well for last 1.5 months until 2 days ago, the production build stopped receiving any notifications. On further analysis of this issue, I found that the exponent push token was not being generated by the getExpoPushTokenAsync() method for android platform alone. IOS still works fine.
I haven’t touched the code regarding notifications for the last 1.5 months. Don’t know how to fix this as it is an important feature of the app. All the FCM configurations have been done properly. The notifications and expo token generation works in development though, when using the expo go app but just not in production.
const registerForPushNotificationsAsync = async () => {
let token;
if (Device.isDevice) {
const { status: existingStatus } =
await Notification.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notification.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
return;
}
token = (await Notification.getExpoPushTokenAsync()).data;
}
if (Platform.OS === "android") {
Notification.setNotificationChannelAsync("default", {
name: "default",
importance: Notification.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C",
});
}
return token; };
useEffect(() => {
// method to check JWT token validity before making API call
let tokenValidity = checkJwtValidity(
jwtToken,
setIsLoggedIn,
setjwtToken,
setUser
);
if (tokenValidity) {
registerForPushNotificationsAsync().then(async (token) => {
// on receiving of token, making an api call to send to backend so they can push the notification to the device using the token
const response = await sendDeviceNotificationToken(jwtToken, token);
console.log(response);
});
}}, []);
Just added one line on my app/build.gradle.file and it started working.
implementation 'com.google.firebase:firebase-iid:17.0.2'
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.
How do I send a notification to another user when one user presses a button? Can someone show me a code snippet?
I realize that this question was asked before, however, it was closed since there were "several answers." The links that were provided that were similar did not explain sending notifications in flutter.
The below solution works, however, my solution is much simpler, and avoids adding new technologies
I have figured out how send a notification to another device using an in app feature.
First, you will need to import the necessary packages:
firebase_messaging
flutter_local_notifications
Note: you will also use the http package
Also note: to send notifications to another device, you must know the device token of that device. I prefer getting the token and saving it in Firestore or Realtime Database. Here is the code to get the device token.
String? mtoken = " ";
void getToken() async {
await FirebaseMessaging.instance.getToken().then((token) {
setState(() {
mtoken = token;
});
});
}
The token will be saved in mtoken, you can now use this as the token for the coming steps.
The next step is to request permission to send push notifications to your app.
void requestPermission() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
} else if (settings.authorizationStatus ==
AuthorizationStatus.provisional) {
print('User granted provisional permission');
} else {
print('User declined or has not accepted permission');
}
}
(If you get "User declined or has not accepted permission" in your console, try going out of your app, finding the icon in the homescreen, pressing and holding on the app icon, tapping "App Info", tapping "Notifications" and turn on "All [app name] notifications."
You will also need two functions to load a Firebase Cloud Messaging notification and one to listen for a notification.
Code to load a Firebase Cloud Messaging notification:
void loadFCM() async {
if (!kIsWeb) {
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
importance: Importance.high,
enableVibration: true,
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
/// Update the iOS foreground notification presentation options to allow
/// heads up notifications.
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
}
And this function to listen for a Firebase Cloud Messaging notifcation.
void listenFCM() async {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null && !kIsWeb) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
// TODO add a proper drawable resource to android, for now using
// one that already exists in example app.
icon: 'launch_background',
),
),
);
}
});
}
You will want to run loadFCM, listenFCM, and requestPermission when the page is initialized.
void initState() {
super.initState();
requestPermission();
loadFCM();
listenFCM();
}
The next step is to find your Firebase Cloud Messaging API key. This can simply be done by heading to your Firebase project > Project Settings > Cloud Messaging then copy the API key under Cloud Messaging API (Legacy).
When you have your Firebase Cloud Messaging API key, this is the code to display a notification given the notification title, body, and device token to send it to.
void sendPushMessage(String body, String title, String token) async {
try {
await http.post(
Uri.parse('https://fcm.googleapis.com/fcm/send'),
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization':
'key=REPLACETHISWITHYOURAPIKEY',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': body,
'title': title,
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done'
},
"to": token,
},
),
);
print('done');
} catch (e) {
print("error push notification");
}
}
Now you can call this function like this:
sendPushMessage('Notification Body', 'Notification Title', 'REPLACEWITHDEVICETOKEN');
I hope this helps.
You will need Firebase Cloud Messaging for that.
The way I've done it is using a Cloud Function that you can trigger via HTTP or even via a Firestore trigger, like this:
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
/**
* Triggered by a change to a Firestore document.
*
* #param {!Object} event Event payload.
* #param {!Object} context Metadata for the event.
*/
exports.messageNotificationTrigger = (change, context) => {
db.collection('users').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
const userData = doc.data();
if (userData.id == '<YOUR_USER_ID>') {
admin.messaging().sendToDevice(userData.deviceToken, {
notification: {
title: 'Notification title', body: 'Notification Body'}
});
}
});
});
};
Every user you have registered in your users collection must have a device token, sent from their device they access the app.
From Flutter, using the FCM package, this is how you send the device token to Firebase:
// fetch the device token from the Firebase Messaging instance
// and store it securely on Firebase associated with this user uid
FirebaseMessaging.instance.getToken().then((token) {
FirebaseFirestore.instance.collection('users').doc(userCreds.user!.uid).set({
'deviceToken': token
});
});
Where userCredentials.user!.uid is the user you use to log in to your application using Firebase Authentication like this:
UserCredential userCreds = await FirebaseAuth.instance.signInWithCredential(credential);
Hope that helps.
I use React Native FCM for messaging, and when the user logs out the application I want to delete the FCM token so that the user doesn't get Notified again.
Below is my code for logout.
_signOutAsync = async () => {
this.logoutEvent()
API.post('customer/auth/logout', null, {
headers: {
Authorization:
'Bearer ' + (await AsyncStorage.getItem(Config.ACCESS_TOKEN))
}
}).then((response) => {
console.log(response)
})
this.clearData()
}
Thanks.
Simply add below given code in your logout function -
for react-native-firebase <= v5.x.x
firebase.messaging().deleteToken()
for > 5.x.x or using #react-native-firebase/messaging
import messaging from '#react-native-firebase/messaging';
messaging().deleteToken()
await firebase.messaging().deleteToken();
is the solution.
BUT, if you get the same token even after deleting, install the npm package react-native-restart, and do the below step to get a new token
messaging()
.deleteToken(undefined,'*')
.then(() => {
RNRestart.Restart();
Install the npm package react-native-restart and Simply call like this:
const logoutAndClearAsyncStorage = async () => {
try {
await AsyncStorage.clear()
await firebase.messaging().deleteToken().then(() => {
RNRestart.Restart()
navigation.replace('LoginStack', { screen: 'WelcomeScreen' });
})
} catch (error) {
console.log(error, 'logout')
}
};
Recently I try to use FCM too, and found the issue usually due to the function comes from, i.e. where to import the functions.
I think you already have installed firebase package, call the function below will trigger delete token on firebase.
import { getMessaging, deleteToken } from 'firebase/messaging';
const messaging = getMessaging(firebaseApp);
deleteToken(messaging);
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.