I have a react native app and I'm trying to implement push notification with the react-native-push-notification package and firebase cloud messaging. It turns out that when I get a notification, the app just stops working. The RemotePushController function seems to work, as soon as the App starts I get the console log with the token. I've tried several things but nothing works.
[]
[
I was able to solve the problem by changing the versions of the googlePlayServicesVersion and firebaseMessagingVersion at android/build.gradle as shown in the image below. I set both to "+".
For Android, no need to set anything in AndroidManifest.xml.
And in the top level build.gradle having the following configuration should be sufficient. Notice the "+" against the versions.
buildscript {
ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 16
compileSdkVersion = 28
targetSdkVersion = 28
googlePlayServicesVersion = "16.+"
androidMapsUtilsVersion = "0.5+"
firebaseVersion = "+"
firebaseMessagingVersion = "+"
}
Here's a hook that I have created for FCM.
import React, { useEffect } from 'react'
import { Alert } from 'react-native'
import messaging from '#react-native-firebase/messaging'
import { useNavigation } from '#react-navigation/native'
import { useAsyncStorage } from '#react-native-community/async-storage'
const useFirebaseCloudMessaging = () => {
const navigation = useNavigation()
const { getItem: getFcmToken, setItem: saveFcmToken } = useAsyncStorage('fcmToken')
const [fcmToken, setFcmToken] = React.useState(null)
const [initialRoute, setInitialRoute] = React.useState('Home')
const getToken = async () => {
const token = await getFcmToken()
if (!token) {
// Get the device token
messaging()
.getToken()
.then(token => {
setFcmToken(token)
saveFcmToken(token)
})
}
}
const requestUserPermission = async () => {
const authStatus = await messaging().requestPermission()
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL
if (enabled) {
console.log('Authorization status:', authStatus)
}
}
useEffect(() => {
// If using other push notification providers (ie Amazon SNS, etc)
// you may need to get the APNs token instead for iOS:
// if(Platform.OS == 'ios') { messaging().getAPNSToken().then(token => { return saveTokenToDatabase(token); }); }
// Listen to whether the token changes
return messaging().onTokenRefresh(token => {
saveFcmToken(token)
})
}, [])
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage))
})
return unsubscribe
}, [])
useEffect(() => {
// Assume a message-notification contains a "type" property in the data payload of the screen to open
messaging().onNotificationOpenedApp(remoteMessage => {
console.log(
'Notification caused app to open from background state:',
remoteMessage.notification
)
navigation.navigate(remoteMessage.data.type)
})
// Check whether an initial notification is available
messaging()
.getInitialNotification()
.then(remoteMessage => {
if (remoteMessage) {
console.log(
'Notification caused app to open from quit state:',
remoteMessage.notification
)
setInitialRoute(remoteMessage.data.type) // e.g. "Settings"
}
})
}, [])
return {
fcmToken,
getToken,
requestUserPermission
}
}
export default useFirebaseCloudMessaging
Use the hook in a top level App component
import React, { useEffect } from 'react'
import Root from './App'
import useFirebaseCloudMessaging from './FirebaseCloudMessaging'
const App = () => {
const {
getToken,
requestUserPermission
} = useFirebaseCloudMessaging()
useEffect(() => {
requestUserPermission()
getToken()
}, [])
return <Root />
}
export default App
Related
I've configured my flutter app in order to receive push notifications, when I try from FCM it works properly and I obtain Heads up notification when the app is in foreground/background/closed.
I configured also the channel and all works.
The problem is when I want to send an automatic notification by using of Google Cloud Function, I never receive the heads up notificationn.
below my code:
Main.Dart
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(...) }
...
#override
void initState() {
LocalNotificationService.initialize(changeMessage);
/// gives you the message on which user taps and it oened app from terminated state
FirebaseMessaging.instance.getInitialMessage().then((event) {
setState(() {
widget.message = event!.data["route"];
});
});
//foreground
FirebaseMessaging.onMessage.listen((event) {
print(event.notification?.body);
print(event.notification?.title);
LocalNotificationService.display(event);
});
// app in background but opened and user taps on notification
FirebaseMessaging.onMessageOpenedApp.listen((event) {
setState(() {
widget.message = event!.data["route"];
});
/*final routeFromMessage = event.data["route"];
print(routeFromMessage);
Navigator.of(context).pushNamed(routeFromMessage);*/
});
}
in Manifest I have:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value = "myChannel"
/>
I created Service
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class LocalNotificationService{
static final FlutterLocalNotificationsPlugin _notificationsPlugin= FlutterLocalNotificationsPlugin();
static void initialize(Function updateRoute){
final InitializationSettings initializationSettings =
InitializationSettings(android: AndroidInitializationSettings("#mipmap/ic_launcher"));
_notificationsPlugin.initialize(initializationSettings,onSelectNotification: (String? route) async{
updateRoute(route);
});
}
static void display(RemoteMessage remoteMessage) async{
try {
final id = DateTime.now().millisecondsSinceEpoch ~/1000;
final NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
"myChannel",
"myChannel channel",
importance: Importance.max,
priority: Priority.high
)
);
await _notificationsPlugin.show(
id, remoteMessage.notification!.title, remoteMessage.notification!.body, notificationDetails,payload: remoteMessage.data["route"]);
} on Exception catch (e) {
print(e);
}
}
}
and my Cloud Function:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().functions);
exports.messageTrigger = functions.firestore.document('events/{likedId}').onCreate(async (snap,context) => {
console.log('----------------start function--------------------')
const doc = snap.data()
console.log(doc)
const idFrom = doc.idFrom //chi mette like
const idTo = doc.idTo //creatore evento
const activity = doc.activity
console.log(`Recupero il TO`)
const res = await admin
.firestore()
.collection('users')
.where('uid', '==', idTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(userTo => {
console.log(`Found user to: "${userTo.data().surname}"`)
console.log(`Recupero il FROM`)
if (userTo.data().pushToken) {
// Get info user from (sent)
admin
.firestore()
.collection('users')
.where('uid', '==', idFrom)
.get()
.then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
console.log(`Found user from: ${userFrom.data().name}`)
const payload = {
notification: {
title: ...,
body: ...,
channel_id: "myChannel",
},
data: {route: ...}
}
// Let push to the target device
admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload)
.then(response => {
console.log('Successfully sent message:', response)
})
.catch(error => {
console.log('Error sending message:', error)
})
})
})
} else {
console.log('Can not find pushToken target user')
}
})
})
return null
});
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// functions.logger.info("Hello logs!", {structuredData: true});
// response.send("Hello from Firebase!");
// });
How I can solve,
thanks
For future readers I solve it by using of android_channel_id: "myChannel" instead of channel_id: "myChannel" in my cloud function
I am working on something where I need to track background location if the app is in background and also if the device is asleep. I currently have it working for app in background but it stops tracking when the device is asleep. I am using Expo for the app and using Expo Task Manager alongside Expo Location to fetch location in background.
Anyone have any idea how to fetch location while app is in background and device is in sleep mode ?
Here's the code
import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const App = () => {
useEffect(() => {
(async () => await _askForLocationPermission())();
});
this.backgroundLocationFetch = async () => {
const { status } = await Location.requestBackgroundPermissionsAsync();
if (status === 'granted') {
console.log('cmon dance with me!')
await Location.startLocationUpdatesAsync('FetchLocationInBackground', {
accuracy: Location.Accuracy.Balanced,
timeInterval: 3000,
distanceInterval: 1,
foregroundService: {
notificationTitle: 'Live Tracker',
notificationBody: 'Live Tracker is on.'
}
});
}
}
const _askForLocationPermission = async () => {
(async () => {
let { status } = await Location.requestBackgroundPermissionsAsync();
if (status !== 'granted') {
setgpsErrorMsg('Permission to access location was denied');
}
})();
};
return(
<View>
<Text></Text>
</View>
)
};
TaskManager.defineTask('FetchLocationInBackground', ({ data, error }) => {
if (error) {
console.log("Error bg", error)
return;
}
if (data) {
const { locations } = data;
console.log("BGGGG->", locations[0].coords.latitude, locations[0].coords.longitude);
}
});
export default App;
I had precisely the same problem and solved this by using and EventEmitter to dispatch the location updates to the UI component.
Top of file:
import EventEmitter from 'EventEmitter'
const locationEmitter = new EventEmitter();
didMount:
locationEmitter.on(LOCATION_UPDATE, (locationData) => {
console.log('locationEmitter locationUpdate fired! locationData: ', locationData);
let coordinatesAmount = locationData.newRouteCoordinates.length - 1;
this.setState({
latitude: locationData.newRouteCoordinates[coordinatesAmount - 1].latitude,
longitude: locationData.newRouteCoordinates[coordinatesAmount - 1].longitude,
routeCoordinates: this.state.routeCoordinates.concat(locationData.newRouteCoordinates)
})
})
componentWillUnmount:
locationEmitter.off(LOCATION_UPDATE);
inside background task definition:
locationEmitter.emit(LOCATION_UPDATE, locationData)
This succesfully sends the location data from the background task, but I'm still stuck in the problem of how to make the background task send location update batches more often. My related post is here.
I have implemented Firebase Push Notifications for my project and Once I do register and do the payment back end will fire push notification for me.
For the first time, the app is installed, and create an account and do payment It will work fine.
But once I kill the app from running apps and reopen it then there will be no notification.
I have created separate class for FCM service and here the code
import {Linking, Platform} from 'react-native';
import navigationService from '../navigation/navigationService';
import {fcmService} from './FCMServiceHelper';
import {localNotificationService} from './NotificationHelper';
import {saveFCMToken} from './tokenUtils';
export default class NotificationHandler {
enableDevicePushNotifications = () => {
fcmService.registerAppWithFCM();
fcmService.register(onRegister, onNotification, onOpenNotificaion);
function onRegister(token) {
saveFCMToken(token);
}
if (Platform.OS == 'android') {
localNotificationService.createChannelAndroid('wapp');
}
function onNotification(notify) {
var RandomNumber = Math.floor(Math.random() * 100) + 1;
const options = {
soundName: 'default',
playSound: true,
};
localNotificationService.showNotification(
RandomNumber,
notify.title,
Platform.OS == 'android' ? notify.body : notify.body,
notify,
options,
'wapp',
);
}
function onOpenNotificaion(notify) {
console.log('🚀 ~ FCM LOG', notify);
}
};
}
On My app, main file everytime the app opens I call this function like this
useEffect(() => {
checkNotifications().then(({status, settings}) => {
if (status == 'granted' && user?.login?.isLoggedIn == true) {
let notificationHandler = new NotificationHandler();
notificationHandler.enableDevicePushNotifications();
}
});
}, []);
This is the firebase config helper file
/*
Firebase Helper File for Push Notifications
*/
import {AppState, Platform} from 'react-native';
import messaging from '#react-native-firebase/messaging';
import * as TokenUtils from './tokenUtils';
import {device} from '../utils/uiUtils';
let deviceToken = null;
class FCMServiceHelper {
register = (onRegister, onNotification, onOpenNotification) => {
this.checkDeviceToken();
this.checkPermission(onRegister);
this.createNotificationListeners(
onRegister,
onNotification,
onOpenNotification,
);
};
checkDeviceToken = async () => {
let async_fcmToken = await TokenUtils.getFCMToken();
if (async_fcmToken !== null) {
deviceToken = async_fcmToken;
}
};
registerAppWithFCM = async () => {
if (Platform.OS === 'ios') {
await messaging().registerDeviceForRemoteMessages();
await messaging().setAutoInitEnabled(true);
}
};
checkPermission = (onRegister) => {
messaging()
.hasPermission()
.then((enabled) => {
if (enabled) {
// User has permissions
this.getToken(onRegister);
} else {
// User doesn't have permission
this.requestPermission(onRegister);
}
})
.catch((error) => {
console.log('[FCMService] Permission rejected ', error);
});
};
getToken = (onRegister) => {
messaging()
.getToken()
.then((fcmToken) => {
if (fcmToken) {
if (deviceToken == null) {
onRegister(fcmToken);
} else {
onRegister(deviceToken);
}
} else {
console.log('[FCMService] User does not have a device token');
}
})
.catch((error) => {
console.log('[FCMService] getToken rejected ', error);
});
};
requestPermission = (onRegister) => {
messaging()
.requestPermission()
.then(() => {
this.getToken(onRegister);
})
.catch((error) => {
console.log('[FCMService] Request Permission rejected ', error);
});
};
deleteToken = () => {
console.log('[FCMService] deleteToken ');
messaging()
.deleteToken()
.catch((error) => {
console.log('[FCMService] Delete token error ', error);
});
};
createNotificationListeners = (
onRegister,
onNotification,
onOpenNotification,
) => {
// When the application is running, but in the background
messaging().onNotificationOpenedApp((remoteMessage) => {
console.log(
'[FCMService] onNotificationOpenedApp Notification caused app to open from background state:',
remoteMessage,
);
if (remoteMessage) {
const notification = remoteMessage.notification;
// onOpenNotification(notification);
// this.removeDeliveredNotification(notification.notificationId)
}
});
// When the application is opened from a quit state.
messaging()
.getInitialNotification()
.then((remoteMessage) => {
console.log(
'[FCMService] getInitialNotification Notification caused app to open from quit state:',
remoteMessage,
);
if (remoteMessage) {
const notification = remoteMessage.notification;
onOpenNotification(notification);
// this.removeDeliveredNotification(notification.notificationId)
}
});
// Foreground state messages
this.messageListener = messaging().onMessage(async (remoteMessage) => {
console.log(
'[FCMService] A new FCM message arrived! foreground',
remoteMessage,
);
if (remoteMessage) {
let notification = null;
let data = remoteMessage.data;
if (Platform.OS === 'ios') {
notification = remoteMessage.notification;
// if (AppState == 'background') {
// onOpenNotification(notification);
// }
} else {
notification = remoteMessage.notification;
}
notification.data = data;
onNotification(notification);
// if (Platform.OS == 'ios') {
// onOpenNotification(notification);
// }
}
});
// Triggered when have new token
messaging().onTokenRefresh((fcmToken) => {
alert('REFRESH TOKEN');
console.log('[FCMService] New token refresh: ', fcmToken);
onRegister(fcmToken);
});
};
unRegister = () => {
this.messageListener();
};
}
export const fcmService = new FCMServiceHelper();
But notification comes only with initial app loading. after kill and re-run, there is no any notifications
I am using react-native-notifications library to implement notification in my app with message cloud firebase , I have followed all guidelines in the documentation,
For react-native-notifications library
For firebase cloud messaging
So when I go to firebase counsel and send a test notification message, nothing happened the app just closed or could call it crash, I have added firebase-analytics to help me with debug what the problem and this what I got when I sent test notification message:
Fatal Exception: java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/android/gms/common/util/zzq;
at com.google.android.gms.gcm.GcmReceiver.onReceive()
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3177)
at android.app.ActivityThread.-wrap18(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1653)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6523)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
here is my code:
import React from 'react'
import { Platform, View } from 'react-native'
import { Notifications } from 'react-native-notifications'
export default class PushNotificationManager extends React.Component {
componentDidMount() {
this.registerDevice()
this.registerNotificationEvents()
}
registerDevice = () => {
Notifications.events().registerRemoteNotificationsRegistered(event => {
// TODO: Send the token to my server so it could send back push notifications...
console.log('Device Token Received', event.deviceToken)
})
Notifications.events().registerRemoteNotificationsRegistrationFailed(event => {
console.error(event)
})
Notifications.registerRemoteNotifications()
}
registerNotificationEvents = () => {
Notifications.events().registerNotificationReceivedForeground((notification, completion) => {
console.log('Notification Received - Foreground', notification)
})
// Calling completion on iOS with `alert: true` will present the native iOS inApp notification.
completion({ alert: false, sound: false, badge: false })
})
Notifications.events().registerNotificationOpened((notification, completion) => {
console.log('Notification opened by device user', notification)
console.log(`Notification opened with an action identifier: ${notification.identifier}`)
completion()
})
Notifications.events().registerNotificationReceivedBackground((notification, completion) => {
console.log('Notification Received - Background', notification)
// Calling completion on iOS with `alert: true` will present the native iOS inApp notification.
completion({ alert: true, sound: true, badge: false })
})
Notifications.getInitialNotification()
.then(notification => {
console.log('Initial notification was:', notification || 'N/A')
})
.catch(err => console.error('getInitialNotifiation() failed', err))
}
render() {
const { children } = this.props
return <View style={{ flex: 1 }}>{children}</View>
}
}
in console.log it consoling the token and in
Notifications.getInitialNotification()
.then(notification => {
console.log('Initial notification was:', notification || 'N/A')
})
it keep consoling 'N/A'
and I wrapped the previous code with the whole app root:
<PaperProvider theme={theme}>
<AuthContext.Provider value={authContext}>
/* here*/ <PushNotificationManager>
<NavigationContainer theme={theme}>
<RootStackScreen />
</NavigationContainer>
/* here*/ </PushNotificationManager>
</AuthContext.Provider>
</PaperProvider>
I've started moving on capacitor. After reading https://capacitorjs.com/docs/cordova/known-incompatible-plugins I found that capacitor doesn't support some cordova plugins.
I'm using cordova-plugin-fcm-with-dependecy-updated for android and cordova-plugin-fcm for iOS in my app for push notifications but capacitor doesn’t support these plugins so I used capacitor native approach guided in https://capacitorjs.com/docs/apis/push-notifications#addlistener.
The native approach is not throwing any error and I am able to get registered token as well but I am not receiving push notifications on registered device.
I also tried https://www.npmjs.com/package/capacitor-fcm but fcm.getToken() is returning null.
capacitor.config.json
"plugins": {
"PushNotifications": {
"presentationOptions": [
"badge",
"sound",
"alert"
]
}
}
app.ts
#Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private fcmService: FcmService
) {
this.initializeApp();
}
ngOnInit() {}
initializeApp() {
this.platform.ready().then(() => {
setTimeout(() => {
this.splashScreen.hide();
}, 300);
this.fcmService.initPush();
});
}
}
fcm.service.ts
import { Injectable } from '#angular/core';
import {
Plugins,
PushNotification,
PushNotificationToken,
PushNotificationActionPerformed,
Capacitor
} from '#capacitor/core';
import { Router } from '#angular/router';
const { PushNotifications, Modals } = Plugins;
import { FCM } from '#capacitor-community/fcm';
const fcm = new FCM();
const { FCMPlugin } = Plugins;
#Injectable({
providedIn: 'root'
})
export class FcmService {
constructor(
private router: Router) { }
initPush() {
alert('Capacitor platform' + Capacitor.platform);
if (Capacitor.platform !== 'web') {
this.registerPush();
}
}
private registerPush() {
PushNotifications.requestPermission().then((permission) => {
if (permission.granted) {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register();
} else {
alert('No permission for push granted');
}
});
PushNotifications.addListener(
'registration',
(token: PushNotificationToken) => {
alert('APN token: ' + JSON.stringify(token));
fcm.getToken().then((r) => {
alert(`FCM Token: ${r.token}`); //---- showing null.
}).catch((err) => {
alert(`FCM Token ERROR: ${JSON.stringify(err)}`);
});
}
);
PushNotifications.addListener('registrationError', (error: any) => {
alert('Registration Error: ' + JSON.stringify(error));
});
PushNotifications.addListener(
'pushNotificationReceived',
async (notification: PushNotification) => {
Modals.alert({
title: notification.title,
message: notification.body
})
}
);
PushNotifications.addListener(
'pushNotificationActionPerformed',
async (notification: PushNotificationActionPerformed) => {
alert('Action performed: ' + JSON.stringify(notification.notification));
}
);
}
}
Is there anything I am missing or I need to add extra configurations to receive push notifications ?
You added SHA certificate fingerprints in your firebase project and update google service file ?