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
Related
I'm creating a chat app (kind of WhatsApp-like messaging) using Flutter.
First, the notifications mechanism is working as intended, whenever I send a message from 1 device to another device, the notification would pop up.
I created a local_notification_service.dart to handle the foreground notification & sending a not
import 'dart:math';
import 'package:get/get.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class LocalNotificationService extends GetConnect {
String serverKey ='xxxxxxxxxxxxxxxxxxxx'
static final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static void initialize() {
const InitializationSettings initializationSettings = InitializationSettings(android: AndroidInitializationSettings("#mipmap/ic_launcher"));
_flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
static void display(RemoteMessage message) async {
try {
print("Display notification");
// int id = DateTime.now().microsecondsSinceEpoch ~/1000000;
Random random = Random();
int id = random.nextInt(1000);
const NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
"mychanel",
"my chanel",
importance: Importance.max,
priority: Priority.high,
));
print("my id is ${id.toString()}");
await _flutterLocalNotificationsPlugin.show(
id,
message.notification!.title,
message.notification!.body,
notificationDetails,
);
} on Exception catch (e) {
print('Error>>>$e');
}
}
Future<void> sendNotification({
String? title,
String? message,
String? token,
String? uniqueId,
String? action,
String? channelId,
String? channelName,
String? channelDesc,
}) async {
final data = {
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"action": action,
"uniqueId": uniqueId,
"message": message,
"channelId": channelId ?? 'my channel id',
"channelName": channelName ?? 'my channel Name',
"channelDesc": channelDesc ?? 'my channel description',
};
try {
final response = await post(
'https://fcm.googleapis.com/fcm/send',
{
'notification': {'title': title, 'body': message},
'priority': 'high',
'data': data,
'to': '$token',
'direct_boot_ok': true,
},
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=$serverKey',
},
);
print('response body : ${response.body}');
} catch (e) {}
}
}
Then, I'm trying to validate the users in my flutter application whenever they receive FCM notification, here's the logic that I want to create:
If the user is not logged in, then the device could not receive the notification
If the user is logged in, but the specific user is not eligible to receive the message (in case there are some users with the same FCM token / device registered ) then the device could not receive the notification. I would want to solve this after the point number 1 is succeeded
Here's my main.dart file
void main() async {
await GetStorage.init();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
GlobalController globalC = Get.put(GlobalController());
AuthController authC = Get.put(AuthController());
ErrorController errorC = Get.put(ErrorController());
ConnectivityResult connectivityResult = ConnectivityResult.none;
final Connectivity connectivity = Connectivity();
connectivityResult = await connectivity.checkConnectivity();
if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) {
// Start FCM
final fcmToken = await FirebaseMessaging.instance.getToken();
globalC.fcmToken.value = fcmToken ?? ''; //set global fcm Token
final FirebaseMessaging fcmInstance = FirebaseMessaging.instance;
NotificationSettings settings = await fcmInstance.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
/* Handle message when in foreground */
FirebaseMessaging.onMessage.listen((event) {
if (globalC.isAuthenticated.isTrue) {
LocalNotificationService.display(event); //display notification
}
});
/* Handle message when in background */
if (globalC.isAuthenticated.isTrue) {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}
fcmInstance.onTokenRefresh.listen((fcmToken) {
// Note: This callback is fired at each app startup and whenever a new
// token is generated.
}).onError((err) {
// Error getting token.
});
// End FCM
}
FirebaseAnalytics analytics = FirebaseAnalytics.instance;
runApp(MyApp());
}
as you can see, I'm trying to filter the non logged in user when in the foreground using the FirebaseMessaging.onMessage.listen with the globalC.isAuthenticated.isTrue validation. And it works (because the default of globalC.isAuthenticated is false whenever user is not logged in)
But for the FirebaseMessaging.onBackgroundMessage function does not seems to work with the validation. I've tried to search for the solution in the documentations, youtube but i couldn't find it till this question is made.
How can I make this kind of validation for background message?
Sorry for this newbie question, any help would be greatly appreciated.
Thank you .
I'm using awesome_notifications and firebase_messaging for notifications.
AwesomeNotifications().actionStream.listen to set a action when the notification is tapped.
Currently when the app is Foreground or Background i can get the payload and open the spectific screen.
My problem is when the app are terminated, i dont know how to get the payload and navigate to some screen.
Here is my code
AwesomeNotifications().actionStream.listen(
(
ReceivedNotification receivedNotification,
) {
final navigation = locator<NavigationService>();
if (receivedNotification.payload != null) {
String? aux = receivedNotification.payload!['args'];
String route = receivedNotification.payload!['route']!;
if (aux == null) {
navigation.navigateTo(route);
} else {
final Map<String, dynamic> args = jsonDecode(aux);
dynamic pageArgs = route == RoutesNames.chatPage
? ChatConversationInfo.fromMap(args)
: AnnouncDetailPageArgs.fromMap(args);
navigation.navigateTo(
route,
arguments: pageArgs,
);
}
}
});
class FirebaseMessagingService {
FirebaseMessagingService();
Future<void> initialize() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
badge: true,
sound: true,
alert: true,
);
_onMessage();
}
// Foreground
_onMessage() {
FirebaseMessaging.onMessage.listen((message) {
print('----- _onMessage');
AwesomeNotifications().createNotificationFromJsonData(message.data);
});
}
Future<String?> getDeviceFirebaseToken() async {
return await FirebaseMessaging.instance.getToken();
}
}
Future<void> _backgroundHandler(RemoteMessage message) async {
print('_backgroundHandler: ${message.data}');
AwesomeNotifications().createNotificationFromJsonData(message.data);
}
void main() async {
setupLocator();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_backgroundHandler);
await locator<AwesomeNotificationsServices>().initialize();
await locator<FirebaseMessagingService>().initialize();
runApp(const MyApp());
}
I am working on work manager to fetxh api data and initiate a notification in flutter,
//this is the name given to the background fetch
const simplePeriodicTask = "simplePeriodicTask";
Workmanager workmanager = Workmanager();
// flutter local notification setup
Future initializeWorkManagerAndPushNotification() async {
await workmanager.initialize(
callbackDispatcher,
isInDebugMode: false,
); //to true if still in testing lev turn it to false whenever you are launching the app
await workmanager.registerPeriodicTask(
"1", simplePeriodicTask,
existingWorkPolicy: ExistingWorkPolicy.replace,
frequency: Duration(minutes: 1), //when should it check the link
initialDelay:
Duration(seconds: 5), //duration before showing the notification
constraints: Constraints(
networkType: NetworkType.connected,
),
);
}
void callbackDispatcher() async {
workmanager.executeTask((task, inputData) async {
print('Ruuning - callbackDispatcher');
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
var androidSettings = AndroidInitializationSettings('#mipmap/ic_launcher');
var iOSSettings = IOSInitializationSettings();
var initSetttings =
InitializationSettings(android: androidSettings, iOS: iOSSettings);
//
// TODO: Permission
//
await flutterLocalNotificationsPlugin.initialize(initSetttings);
String message =
await WebServiceController.getInstance.getPushNotificationMessage();
print("here 1 ================");
// print(response);
// var convert = json.decode(response.body);
// if (convert['status'] == true) {
// showNotification(convert['msg'], flutterLocalNotificationsPlugin);
// } else {
// print("no messgae");
// }
print("messgae");
print(message);
await showNotification(message, flutterLocalNotificationsPlugin);
return Future.value(true);
});
}
Future showNotification(payloadmessage, flutterLocalNotificationsPlugin) async {
print("showing notification");
var androidDetails = AndroidNotificationDetails(
'channel id', 'channel NAME', 'CHANNEL DESCRIPTION',
priority: Priority.high, importance: Importance.max);
var iOSDetails = IOSNotificationDetails();
var platform = NotificationDetails(android: androidDetails, iOS: iOSDetails);
await flutterLocalNotificationsPlugin.show(
0, 'Message - Virtual intelligent solution', '$payloadmessage', platform,
payload: 'OnClick Payload -VIS \n $payloadmessage');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
await initializeWorkManagerAndPushNotification();
runApp(MyApp());
}
this function callbackDispatcher() function is used to Initialize Notification and fetch api data from below method.
Future<String> getPushNotificationMessage() async {
print("getting - getPushNotificationMessage");
try {
var response = await _dio.get(APIURLConstants.pushNotification);
print(response);
print("here================ ${response.data}");
var convert = json.decode(response.data);
return convert['message'].toString() ?? "";
} on DioError catch (error) {
print("Dio Error ${error.message}");
} catch (error) {
print("Error - ${error.toString()}");
}
}
as per the below output , It seems that only print(response); of getPushNotificationMessage() is executed after that below code is not executing,
Performing hot restart...
Restarted application in 2,266ms.
W/WM-WorkSpec(31092): Interval duration lesser than minimum allowed value; Changed to 900000
I/flutter (31092): getting - getPushNotificationMessage
I/flutter (31092): {"message":"Hi There I am notification","seen":0,"notification_id":"111"}
I/WM-WorkerWrapper(31092): Worker result SUCCESS for Work [ id=6c935b3b-bfe4-47fa-b30a-0965299f5224, tags={ be.tramckrijte.workmanager.BackgroundWorker } ]
I founded the issue, to make the code execute, I needed to hot restart the application to execute code from
main() method
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 have this code that works correctly but I want to add the 'mensaje' in the body of the notification, the problem is that I do not know how to get it to be able to send it.
This is the structure of my data in firebase:
enter image description here
And this is the function:
const functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPush = functions.database.ref('/chats/{chat}/{mensaje}/').onWrite((snapshot, context) => {
nombreChat = context.params.chat;
return cargarUsuarios(nombreChat).then(usuarios => {
let tokens = [];
for (let user of usuarios){
if(user.token !== undefined){
console.log('User', "Usuario: " + user.nombre);
console.log('User token', "Token: " + user.token);
tokens.push(user.token);
}
}
let payload = {
notification:{
title:'Trado GO',
body: 'Has recibido un nuevo mensaje',
sound: 'default',
badge: '1'
}
};
return admin.messaging().sendToDevice(tokens, payload);
});
});
function cargarUsuarios(chat){
var arrayParticipantesChat = chat.split(',');
let dbRef = admin.database().ref('/usuarios');
let defer = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let data = snap.val();
let usuarios = [];
for (var property in data){
usu=data[property];
if(arrayParticipantesChat.includes(usu['nombre'])){
usuarios.push(data[property]);
}
}
resolve(usuarios);
}, (err) => {
reject(err);
});
});
return defer;
}