I am implementing expo-notifications into my react native app. Notifications have been working fine up until a couple weeks ago and I can't figure out what the issue is.
Everything on Android is working properly, but on iOS I'm not able to receive notifications. I've checked and I have notifications allowed in my settings and am receiving a "valid" ExpoPushToken because I was able to put that push token into their testing tool without error. I also tested it myself on postman using their api and received a status of "ok", but still didn't see a notification on my phone.
Here's my code for getting the token:
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: true,
allowSound: true,
},
});
finalStatus = status;
}
if (finalStatus !== "granted") {
// alert("Failed to get push token for push notification!");
return;
}
token = (
await Notifications.getExpoPushTokenAsync({
experienceId: "#username/app-slug",
})
).data;
console.log(token);
}
if (Platform.OS === "android") {
Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C",
});
}
return token;
}
Obviously #username and app-slug are my username and slug for my expo project.
The only thing that I know has changed is I've upgraded to Expo SDK 46 and I added in the experienceId into the getExpoPushTokenAsync function. Could adding experienceId screw up receiving notifications? Or is there something obvious that I'm missing here?
I figured out my issue. I had multiple Push Keys that were set up in my Apple Developer account. I revoked both and had expo generate a new one using eas credentials and that fixed my issue.
Related
I am trying to authenticate through Azure Active Directory on a react native app using react-native-app-auth package which is the also the official package for this task. I have made the app registration, successfully made it to return me the "auth code" instead of "auth token", also I was able to make the mobile app redirected to the required screen. But right after returning me the "auth code" it says
[Error: Data intent is null]
The code I am using is below
import { authorize } from "react-native-app-auth";
import pkceChallenge from 'react-native-pkce-challenge';
const config = {
clientSecret: "MY_SECRET_VALUE_FROM_AAD",
issuer: 'https://login.microsoftonline.com/MY_TENANT_VALUE_FROM_AAD',
clientId: 'MY_CLIENT_ID_AT_AAD',
redirectUrl: 'com.sham://com.sham/android/callback',
scopes: [
'openid',
'profile',
'email',
],
additionalParameters: {prompt: 'select_account'},
serviceConfiguration: {
authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
},
};
try {
const result = await authorize(config);
} catch (err){
console.log(err)
}
// ALSO THROUGH
const challenge = pkceChallenge();
try {
const result = await authorize({...config, code_challenge: challenge.codeChallenge, code_challenge_method: "S256"});
} catch (err){
console.log(err)
}
I have also tried to install the demo app at https://github.com/microsoftgraph/msgraph-training-react-native but that app never gets installed maybe due to old dependencies. Please point out if someone knows what mistake I might be commiting.
I have an Ionic 5 app with Capacitor 3 and I'm trying to receive notifications using Firebase Cloud messaging, on an Android device. I followed the configurations,(I downloaded the google JSON file and put my app id name correctly ) and I'm getting correctly the device token. Once my app is open I get the token successfully without any error and then I send a notification sharing my token to the Firebase test message, the notification never arrived, and also I never get an error of push notification in my logger. This is the code that I use for push notification.
export class PushNotificationsService {
constructor(private readonly http: HttpClient, private notificationState: NotificationsStore) { }
public initPush() {
if (Capacitor.getPlatform() !== 'web') {
this.registerPush();
}
}
private registerPush() {
PushNotifications.requestPermissions().then(async result => {
if (result.receive === 'granted') {
// Register with Apple / Google to receive push via APNS/FCM
console.log('granted');
await PushNotifications.register();
} else {
// Show some error
console.log('errorr');
}
});
// On success, we should be able to receive notifications
PushNotifications.addListener('registration',
(token: Token) => { (I get through this step and access the token successfully)
console.log('Push registration success, token: ' + token.value);
this.notification state.setToken(token.value);
}
);
// I never get this step error
PushNotifications.addListener('registrationError',
(error: any) => {
console.log('Error on registration: ' + JSON.stringify(error));
}
);
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotificationSchema) => {
console.log('Push received: ' + JSON.stringify(notification));
}
);
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: ActionPerformed) => {
console.log('Push action performed: ' + JSON.stringify(notification));
}
);
}
I also have capacitor.config.json like this:
"PushNotifications": {
"presentationOptions": ["badge", "sound", "alert"]
}
Also, I checked that my device and app have permission for notifications and are enabled both. I tried and test this issue with my app open and closed and open only in the background and the notification never arrives. What it could be? Any clue? Thank you
Android Phone Please Create the channel Like this, Test or Add screenshot for console Error
if (Capacitor.getPlatform() === 'android') {
PushNotifications.createChannel({
id: 'fcm_default_channel',
name: 'app name',
description: 'Show the notification if the app is open on your device',
importance: 5,
visibility: 1,
lights: true,
vibration: true,
});
}
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.