I am using firebase messaging with local notifications but FirebaseMessaging.onMessageOpenedApp and FirebaseMessaging.instance .getInitialMessage is not working as I want to open the app on notification click.
Here is my code:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
log("onMessageOpenedApp is fired");
await flutterLocalNotificationsPlugin.cancelAll();
LoginStorage storage = LoginStorage();
log("storage.getIsLogin() = ${storage.getIsLogin()}");
if (storage.getIsLogin() == "true") {
Get.off(() => ChatPage(
roomId: message.data["room_id"],
roomName: message.data["roomname"],
totalMsgs: "0",
roomType: message.data["room_type"],
));
} else {
Get.off(() => LoginPage());
}
log('A new onMessageOpenedApp event was published!');
});
Try this code
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message){
if(message!=null){
log("onMessageOpenedApp is fired");
await flutterLocalNotificationsPlugin.cancelAll();
LoginStorage storage = LoginStorage();
log("storage.getIsLogin() = ${storage.getIsLogin()}");
if (storage.getIsLogin() == "true") {
Get.off(() => ChatPage(
roomId: message.data["room_id"],
roomName: message.data["roomname"],
totalMsgs: "0",
roomType: message.data["room_type"],
));
} else {
Get.off(() => LoginPage());
}
log('A new onMessageOpenedApp event was published!');
}
});
You wrote the perfect code, but this issue is not about the code. This happened because your app is in foreground when you fire the notification,
the app needs to be terminated or in the background at this time.
Also add following code it help when your app have a fresh start
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
//Navigate Here
});
Hope this helps you...
Related
//root dart file
FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);
await NotificationService.instance.initializeNotifications();
Future<void> _backgroundMessageHandler(RemoteMessage message) async {
await Firebase.initializeApp();
}
//In, NotificationService file, I have initialized
AwesomeNotification(awesome_notification package),and have
//This handles notification actions
AwesomeNotifications().actionStream.listen((notification) async {
print(notification.payload);
if (notification.payload != null) {
final payload = notification.payload!;
if (payload['type'] == 'vaccine-records') {
_appRouter.root.innerRouterOf(DashboardRouter.name)
?..innerRouterOf<TabsRouter>(DashboardRoute.name)?.setActiveIndex(2)
..navigate(
VaccineRecordsRouter(
petId: int.parse(payload['id']!),
petType: int.parse(payload['pet_type']!),
petName: notification.title,
),
);
}
}
});
//this listens to new notification from Firebase cloud messaging
FirebaseMessaging.onMessage.listen((message) async {
print(message.data);
if (message.data.isNotEmpty) {
await AwesomeNotifications().createNotificationFromJsonData(message.data);
} else {
print('here');
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: 0,
channelKey: 'basic_channel',
title: message.notification?.title,
body: message.notification?.body,
showWhen: true,
displayOnForeground: true,
displayOnBackground: true,
),
);
}
});
}
When I tap on the notification, it takes me to homepage of my app. I want it to
navigate me to some other screen.When the app is in the foreground and I receive the
notification, it takes me to the desired page. But when the app is in the background
and the notification is received, it takes me to the homepage.How is this happening
since the both time i get AwesomeNotifications().actionStream.listen((notification)
async {} to execute?
I would suggest you use Firebase dynamic links to send the users to specific page based on the payload of notification.
In your current case,
onMessage function is triggered only when the app is in foreground.
Notifications must be handled for 3 states.
When app is in foreground
When app is in background
When app is terminated
Use the following :
//when app is terminated
FirebaseMessaging.instance.getInitialMessage().then((value) {
if (value != null) {
_onNotificationMessage(context, value); // custom function to handle notification
}
});
//when app is in foreground
FirebaseMessaging.onMessage.listen((message) {
_onProcessMessage(context, message); // custom function to handle notification
});
//when app is in background
FirebaseMessaging.onMessageOpenedApp.listen((message) {
_onNotificationMessage(context, message); // custom function to handle notification
});
}
I am using Expo 37 with expo-google-app-auth 8.1.0. I am able to sign in users successfully. But in iOS, when a user clicks "cancel", whether in the Alert or the browser window, I get an error:
ERR_APP_AUTH: The operation couldn’t be completed. (org.openid.appauth.general error -3.)
This happens for both the simulator and standalone apps - again only for iOS. Why isn't it just returning an object with "type" : "cancel"?
Implementation of Google login method below:
signInWithGoogle = async (): Promise<void> => {
try {
const result = await Google.logInAsync({
androidClientId: ANDROID_CLIENT_ID,
iosClientId: IOS_CLIENT_ID,
androidStandaloneAppClientId: ANDROID_STANDALONE_CLIENT_ID,
iosStandaloneAppClientId: IOS_STANDALONE_CLIENT_ID,
scopes: ['profile', 'email'],
});
if (result.type === 'success') {
await firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL);
const credential = firebase.auth.GoogleAuthProvider.credential(result.idToken, result.accessToken);
const googleProfileData = await firebase.auth().signInWithCredential(credential);
if (googleProfileData.user.uid) {
this.props.setShouldBeLoggedOut(false);
this.props.setShouldPerformLogout(false);
} else {
Alert.alert('Unable to sync Google credentials with Authentication server');
}
}
} catch (error) {
Alert.alert('Google Login Error:', error.message);
}
};
If you remove Alert.alert('Google Login Error:', error.message);, App will not crash. I think it might have to do something with error.message?
I ran into the same issue. The best I could do was to check e.code before raising the Alert:
try {
const { type, accessToken } = await Google.logInAsync({
androidClientId: GOOGLE_ANDROID_CLIENT_ID,
androidStandaloneAppClientId: GOOGLE_ANDROID_STANDALONE_CLIENT_ID,
iosClientId: GOOGLE_IOS_CLIENT_ID,
iosStandaloneAppClientId: GOOGLE_IOS_STANDALONE_CLIENT_ID,
});
if (type === "success") {
this._handleAppLogin(accessToken);
}
} catch (e) {
if (e.code != -3) {
Alert.alert("Google Login Error", e.message);
}
}
However, I couldn't find documentation that specified what error codes were possible under what conditions, so this solution might suppress other real errors.
Is it possible to navigate to a specified path when clicking background FCM notification?
I created a Top-Level function and add it to the navigator path but its not working, when clicking on a background notification, it just opens the app
I GUESS I FOUND AN ISSUE
Now, I changed fcm configuration from home page to splash screen.
The foreground doesn't navigate to the page, I think its because the Splash Screen is no longer available. When I click on the notification message, it just opens the app.
FCM Configuration
onBackgroundMessage: backgroundMessageHandler
Top-Level function
Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
if (message.containsKey('data')) {
getIt<NavigationService>().navigateTo('/${message['data']['screen']}');
}
}
Payload
const payload: admin.messaging.MessagingPayload = {
notification:{
title: `New Enquiry`,
body:`${customerName} published to ${subName}`,
badge: '1',
sound: 'default'
},
data: {
click_action: `FLUTTER_NOTIFICATION_CLICK`,
sound: `default`,
status: `chat`,
screen: `homePage`
}
}
main.dart
GetIt getIt = GetIt.instance;
void main() {
setupLocator();
runApp(MyApp());
}
MaterialApp
return MaterialApp(
navigatorKey: NavigationService().navigatorKey,
onGenerateRoute: Router.generateRoute,
);
NavigationService and setupLocator
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState.pushNamed(routeName);
}
}
void setupLocator() {
getIt.registerLazySingleton(() => NavigationService());
}
You can use this function to navigation when the app is killed and receive notifications in the background
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
print("FirebaseMessaging.getInitialMessage");
if (message != null) {
Navigator.of(context).pushNamed('/call');
}
});
This function only run once when the app open and get the last message.
You can read more at this doc: https://github.com/FirebaseExtended/flutterfire/blob/62e09975f9b3d14141585e62ddab6b98e667b7cf/docs/messaging/notifications.mdx
To handle notifications coming from a background state you can use the stream exposed by the FirebaseMessaging library.
FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
// Handle navigation or perform any logic here
});
I hope you have configured the required native side declaration required by firebase_messaging specfied here
Based on the payload you have given :
Payload
const payload: admin.messaging.MessagingPayload = {
notification:{
title: `New Enquiry`,
body:`${customerName} published to ${subName}`,
badge: '1',
sound: 'default'
},
data: {
click_action: `FLUTTER_NOTIFICATION_CLICK`,
sound: `default`,
status: `chat`,
screen: `homePage`
}
}
Have your onResume like this, similarly same for onLaunch
onResume :
onResume: (Map<String, dynamic> message) {
if(SharedClassID.toString().contains(message['data']['classID']))
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>BroadcastNotification()));
print("found");
}
else if(SharedClassID.toString().contains(message['data']['classID']))
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>StudentHomework()));
print("found");
}
else
{
print("not found");
}
//print(message['data']['classID']+" onResume");
print('onResume: $message');
return;
}
I can find out the reason why it is not working because in the payload the key is screen:'homePage' and i can notice that you are not passing classID so what is happening is it failing to evaluate into the if else condition you have added.
You update the if/else condition like below :
if(message['data']['screen'] == 'homePage')
{
//TODO: route
}else if(message['data']['screen'] == 'homeWorkPage'){
//TODO: route
}
If you want to route based on the SharedClassId then pass the classId in the payload and route.
example snippet :
if(message['data']['classId'] == SharedClassId)
{
//TODO: route
}else if(message['data']['classId'] == SharedClassId){
//TODO: route
}
In that example I used shared preference data like class id to check notification data contain this class id then it can navigate to a particular screen
void registerNotification() {
_firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true),
);
_firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) {
print('onMessage: $message');
// print(message['data']['classID']+" onMessage");
if(SharedClassID.toString().contains(message['data']['classID']))
{
Navigator.push(context, MaterialPageRoute(builder:
(context)=>BroadcastNotification()));
print("found");
}
else if(SharedClassID.toString().contains(message['data']['classID']) )
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>MuseGalaryNew()));
print("found");
}
else
{
print("not found");
}
return;
}, onResume: (Map<String, dynamic> message) {
if(SharedClassID.toString().contains(message['data']['classID']))
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>BroadcastNotification()));
print("found");
}
else if(SharedClassID.toString().contains(message['data']['classID']))
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>StudentHomework()));
print("found");
}
else
{
print("not found");
}
//print(message['data']['classID']+" onResume");
print('onResume: $message');
return;
}, onLaunch: (Map<String, dynamic> message) {
if(SharedClassID.toString().contains(message['data']['classID']) )
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>BroadcastNotification()));
print("found");
}
else if(SharedClassID.toString().contains(message['data']['classID']))
{
Navigator.push(context, MaterialPageRoute(builder: (context)=>StudentHomework()));
print("found");
}
else
{
print("not found");
}
return;
});
}
Latest Code as of now AUG 2021.
First of all your project dart version and firebase plugin version are important things to integrate firebase notification. If you are using a lower version, then you may face some issues. For my flutter project, I am using the latest null safe Firebase messaging plugin. If you use Flutter messaging 10.0.4 or higher version then you don't need to add any metadata on the android manifest file. You can see the complete code here.
firebase_messaging: ^10.0.4
firebase_core: ^1.4.0
flutter_local_notifications: ^8.1.1
if the flutter app is closed then the below code will be called. It will be called only once & you should provide click_action as "FLUTTER_NOTIFICATION_CLICK" as custom data on the firebase console cloud messaging window.
// when app is closed and it will be called only once
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
print("fireabse closed app msg");
if (message != null) {
print( " firebase msg received closed add "+message.data.toString());
Navigator.push(context, MaterialPageRoute(builder: (context){ return product(prodid: message.data["prodid"]);}));
/* Navigator.pushNamed(context, '/message',
arguments: MessageArguments(message, true));
*/
}
});
To display the notification icon on the status bar uses the below code.
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
print(" firebase msg - "+message.data.length.toString());
if (notification != null && android != null) {
print("firebase msg body "+ notification.body.toString());
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channel.description,
// TODO add a proper drawable resource to android, for now using
// one that already exists in example app.
icon: 'noti_icon',
color: Colors.red
),
));
}
});
I am trying to send notifications from firebase console to my react-native app
I followed the poor documentation here as much as I understand: https://invertase.io/oss/react-native-firebase/v6/messaging/quick-start
I installed #react-native-firebase/app and /messaging and here is my code in component:
componentDidMount() {
this.reqNotifications()
this.checkNotificationPermission()
}
reqNotifications() {
requestNotifications(['alert', 'badge', 'sound']).then(({status, settings}) => {
console.log('NOTIFICATION STATUS' + status)
});
}
async checkNotificationPermission() {
const enabled = await messaging().hasPermission();
if (enabled) {
console.log('APPROVED');
await messaging().registerForRemoteNotifications()
messaging().getToken().then(token => console.log('token: >> ' + token))
} else {
console.log('NOT APPROVED');
}
}
I am requesting permission via react-native-permissions and permission request is
working.
My Apple APNs are OK on Apple and Firebase console
And I am getting my token by getToken() method on the code
succesfully.
But I cant send anything to device from firebase; nothing happening on neither foreground nor background . I tried with-token test and also tried normal but no, nothing happens.
I added this code to componentDidMount:
messaging().onMessage(async remoteMessage => {
console.log('FCM Message Data:', remoteMessage.data);
});
As I understand this subscribes for cloud messages and when I send some cloud message notification from firebase-console, I should get console output; but nothing happens.
I dont know what am I missing but I think there is a big update on this package and most of docs are for previous version and I really stuck here thanks for assist
for rnfirebase.io V6
componentDidMount = async () => {
this.checkNotificationPermission();
await messaging().requestPermission({provisional: true});
await messaging().registerDeviceForRemoteMessages();
await this.getFCMToken();
if (Platform.OS === 'android') {
this.createAndroidNotificationChannel();
}
this.backgroundState();
this.foregroundState();
};
checkNotificationPermission = () => {
firebase
.messaging()
.hasPermission()
.then(enabled => {
if (!enabled) {
this.promptForNotificationPermission();
}
});
};
promptForNotificationPermission = () => {
firebase
.messaging()
.requestPermission({provisional: true})
.then(() => {
console.log('Permission granted.');
})
.catch(() => {
console.log('Permission rejected.');
});
};
createAndroidNotificationChannel() {
const channel = new firebase.notifications.Android.Channel(
'channelId',
'Push Notification',
firebase.notifications.Android.Importance.Max,
).setDescription('Turn on to receive push notification');
firebase.notifications().android.createChannel(channel);
}
foregroundState = () => {
const unsubscribe = messaging().onMessage(async notification => {
console.log('Message handled in the foregroundState!', notification);
});
return unsubscribe;
};
// Register background handler
backgroundState = () => {
messaging().setBackgroundMessageHandler(async notification => {
console.log('Message handled in the background!', notification);
});
};
I'm using Expo to develop both Android and iOS at same time. Notifications were working fine for several weeks, and then out of no where stopped working in production, even though I did not update the app during this time.
Server-side, everything is still fine, and notifications are being pushed. In dev, notifications are still being received and handled properly, but in production, it's crickets.
Has anyone else experienced this / what could be causing this?
Here is my code:
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
notificationsSet: false,
}
}
componentDidMount() {
this.registerForPushNotificationsAsync(this.props.currentUser.currentUser.id, this.props.currentUser.authToken)
savePushToken = (userId, pushToken, token) => {
//API call to save push token to database
apiHelper
.savePushToken(userId, pushToken, token)
.then(res => {
return
})
.catch(err => console.log("err saving", err));
};
handleNotification = notification => {
this.props.setNotification({ notification })
}
registerForPushNotificationsAsync = async (userId, token) =>{
//requesting if user would like to turn on notifications
const { status: existingStatus } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
//this checks if notifications is turned on for the app --- "granted"
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
finalStatus = status;
}
if (finalStatus !== "granted") {
return;
} //if "granted" then get push notifications and calls this.savepushtoken to save into the API
let pushToken = await Notifications.getExpoPushTokenAsync();
this.subscription = Notifications.addListener(this.handleNotification);
this.savePushToken(userId, pushToken, token);
};
render() {
return(...)
}
}