Flutter FirebaseMessaging onMessage not working - firebase

I am trying to push a notification to an IOS flutter app. My flutter main loop looks like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
print(await FirebaseMessaging.instance.getToken());
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
runApp(MyApp());
}
And the initState() of MyApp() looks like this:
#override
void initState() {
super.initState();
FirebaseMessaging messaging = FirebaseMessaging.instance;
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
}
});
}
I can't get any of the FirebaseMessaging streams to work. The onMessage/onMessageOpenedApp/onBackgroundMessage() won' work but if the App is closed I can push notifications. But still none of the above streams will have any content.
I push notifications via this python code:
import firebase_admin
from firebase_admin import credentials
from firebase_admin import messaging
cred = credentials.Certificate("auth.json")
app = firebase_admin.initialize_app(cred)
topic = 'test'
apns_config = messaging.APNSConfig(
payload=messaging.APNSPayload(
messaging.Aps(
mutable_content=False,
sound=messaging.CriticalSound('default')
)
)
)
notification = messaging.Notification(
title="FCM Message",
body="This is a Firebase Cloud Messaging Topic Message!"
)
# See documentation on defining a message payload.
message = messaging.Message(
data={
'score': '850',
'time': '2:45',
},
notification=notification,
#topic=topic,
token="token_of_device",
apns=apns_config
)
response = messaging.send(message, app=app)
print('Successfully sent message:', response)
Is there a way to fix this problem?
Thanks in advance.

To test notifications on IOS you must have:
Mac (to run from XCode), IPhone (will not work on emulator, because it is the politics of apple: "get all the money from user"), apple dev account (100$/year), set up certificates, ask user for permissions and then test on real Iphone device

Related

Flutter FCM data only payload notification is not received when app is terminated

I'm using firebase_messaging: ^11.2.10 and flutter_local_notifications: ^9.4.0 to show notifications. I am sending data only notifications without notification title or body. Most of the time it works, but when the app is terminated and not used for a longer period, notifications won't show until you wake or unlock the phone.
Here is the case:
I subscribe to the Firebase background messages.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(MessagingService.subscribeToFCMBackground);
await NotificationService().init();
runApp(const MyApp());
);
}
Inside my MessagingService I get the payload data and there it shows the notification based on the value under the "type" key. Looks like this:
static Future<void> subscribeToFCMBackground(RemoteMessage message) async {
await Firebase.initializeApp();
NotificationService().showNotificationByType(_getNotificationTypeFromMessage(message));
}
static NotificationType _getNotificationTypeFromMessage(final RemoteMessage message) {
final String notificationType = message.data['type'] as String;
return notificationTypeEnumMap[notificationType]!;
}
Inside the NotificationService I create notification channel, place it inside of NotificationDetails object and pass it to showNotification method. Here is an example of android channel:
final AndroidNotificationDetails _androidNotificationDetails = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Channel',
channelDescription: 'Very important notifications',
playSound: true,
sound: RawResourceAndroidNotificationSound(soundFileName),
priority: Priority.max,
importance: Importance.max,
);
...
final NotificationDetails platformChannelSpecifics = NotificationDetails(
android: _androidNotificationDetails,
iOS: _iOSNotificationDetails,
);
...
await flutterLocalNotificationsPlugin.show(
0,
'Notification title',
'Notification body',
platformChannelSpecifics,
);
Also I added this to the Android Manifest file:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
Do you guys have any idea how to solve this problem? I would really appreciate any advice or a solution. Thank you!

Push Notifications not connecting to firebase messaging properly

I am attempting to test out push notifications on my Iphone simulator. For some reason none of my tests seems to be going through. With the new updates on firebaseMessaging I also cannot figure out how to properly print the test notification to my terminal to see if it is connected properly. I am new to flutter and following a very outdated Udemy class. If anyone could maybe give some advice on where I may be going wrong, I would greatly appreciate it. Thank you in advance. Below is my pushnotification service.dart file.
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:driver/datamodels/jobdetails.dart';
import 'package:driver/globalvariables.dart';
import 'dart:io';
import 'package:assets_audio_player/assets_audio_player.dart';
import 'package:driver/widgets/ProgressDialog.dart';
class PushNotificationService {
final FirebaseMessaging fcm = FirebaseMessaging.instance;
Future initialize(context) async {
if (Platform.isIOS) {
fcm.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
}
FirebaseMessaging.onMessage.listen((event) {
// fetchJobInfo(getJobID(message), context);
(Map<String, dynamic> message) async => fetchJobInfo(getJobID(message), context);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// fetchJobInfo(getJobID(message), context);
(Map<String, dynamic> message) async => fetchJobInfo(getJobID(message), context);
});
}
Future<String> getToken() async {
String token = await fcm.getToken();
print('token: $token');
DatabaseReference tokenRef = FirebaseDatabase.instance.reference().child('drivers/${currentFirebaseUser.uid}/token');
tokenRef.set(token);
fcm.subscribeToTopic('alldrivers');
fcm.subscribeToTopic('allusers');
}
String getJobID(Map<String, dynamic> message){
String jobID = '';
if(Platform.isAndroid){
jobID = message['data']['job_id'];
}
else{
jobID = message['job_id'];
print('job_id: $jobID');
}
return jobID;
}
void fetchJobInfo(String jobID, context) {
//show please wait dialog
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) =>
ProgressDialog(status: 'Fetching Details...',),
);
DatabaseReference jobRef = FirebaseDatabase.instance.reference().child(
'jobRequest/$jobID');
jobRef.once().then((DataSnapshot snapshot) {
Navigator.pop(context);
final assetAudioPlayer = AssetsAudioPlayer();
if (snapshot.value != null) {
assetAudioPlayer.open(Audio('sounds/sounds_alert.mp3'));
assetAudioPlayer.play();
double destinationLat = double.parse(
snapshot.value['destination']['latitude'].toString());
double destinationLng = double.parse(
snapshot.value['destination']['longitude'].toString());
String destinationAddress = snapshot.value['destination_address'];
String paymentMethod = snapshot.value['payment_method'];
JobDetails jobDetails = JobDetails();
jobDetails.jobID = jobID;
jobDetails.destinationAddress = destinationAddress;
jobDetails.destination = LatLng(destinationLat, destinationLng);
jobDetails.paymentMethod = paymentMethod;
// showDialog(
// context: context,
// barrierDismissible: false,
// builder: (BuildContext context) => NotificationDialog(jobDetails: jobDetails,),
// );
}
});
}
}
There is a way how you can push notifications to simulator manually with Xcode cli and this way even looks easier to test notifications for Flutter/FireBase.
First you have to set up notification payload file e.g. notification.apns. The contents of the file:
{
"Simulator Target Bundle": "<Your App bundle identifier>",
"gcm.message_id": "random string",
"aps": {
"alert": "Push Notifications Test",
"sound" : "default",
"badge": 1,
},
"dataKey": "any data value to ise in the app"
}
The key is defining a property gcm.message_id - because FireBase Messaging service checks for it and only executes callbacks if it is present.
Once you have it ready, just run xcrun simctl push booted notification.apns and all the FBM callbacks will be executed.
Also, you can learn more about simulating notifications in this answer: How can I test Apple Push Notification Service without an iPhone?
Please see the latest doc link is below:-
Flutter Notifications
It is already answered, but with a picture. You can't get notifications to an iPhone simulator.
For iOS; you must have a physical iOS device to receive messages.
Firebase Cloud Messaging integrates with the Apple Push Notification service (APNs), however APNs only works with real devices.

How to send FirebaseMessaging dynamically to all users when one action is done - Flutter?

I have a flutter app that in some point the administrator users can save one publication.
Now I want all users receive a notification when that publication is posted (with it title, description ..etc).
How could I do that with firebase messaging?
I already wrote this code which, if I go to firebase console and generate a example notification, I receive it normally:
class PushNotificationsManager {
PushNotificationsManager._();
factory PushNotificationsManager() => _instance;
static final PushNotificationsManager _instance = PushNotificationsManager._();
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
Future<void> init() async {
if(Platform.isIOS){
_firebaseMessaging.requestNotificationPermissions(IosNotificationSettings());
}
_firebaseMessaging.configure(
// Called when the app is in the foreground and we receive a push notif.
onMessage: (Map<String, dynamic> message) async {
print("onMessage: ${message}");
print(message['notification']['body']);
},
// Called when the app has been closed completely and its opened
// from the notification directly
onLaunch: (Map<String, dynamic> message) async {
print("onMessage: ${message}");
},
// Called when the app is in the background and its opened from the notif
onResume: (Map<String, dynamic> message) async {
print("onMessage: ${message}");
},
);
}
In summary, how could I generate a notification (with the title and description created) to all users when the admin creates a new publication without going to firebase console to generate it manually?
I'm using firebase_messaging: ^7.0.3
Update
I tried to do this:
Future sendNotification() async {
final String url = 'https://fcm.googleapis.com/fcm/send';
var token = await _firebaseMessaging.getToken();
var data;
data='{"notification": {"body": "this is a body", "title": "this is a title"}, "priority": "high", "data": {"click_action": "FLUTTER_NOTIFICATION_CLICK"}, "to": "${token}"}';
final response = await http.post(
url,
headers: <String, String>{"Content-Type": "application/json", "Keep-Alive" : "timeout=5", "Authorization" : "key=${mykey}"},
body: data
);
print(response.body);
}
...calling this in the method I save the event in firebase only displays the notification to my phone, and not to every phone, there's a way to do it in this form?
You can do this using a cloud function. Either by calling the function from your app when the publication is created, or by having the cloud function listen for a new document. See this Medium post for some ideas: https://medium.com/#jerinamathews/send-firebase-cloud-messaging-fcm-to-a-topic-using-cloud-function-when-realtime-database-value-fa78fa758549
Update
Based on your update, I suggest you look at using a 'Topic' rather than a specific token (that applies to only one device). To use a Topic you need to ensure all users automatically subscribe to the chosen topic when they open the app (they can subscribe to the same topic each time, it has no negative impact). FCM maintains a list of subscribed device tokens against the topic.
I haven't used topic via an http post so I cannot confirm it is possible but I am assuming if you can send to a token you must be able to send to a topic.

Firebase Messaging Get Cloud Function Name

I have 2 different cloud functions that send a notification to the device. one is sendNewChatMessageNotification and the other one is sendNewFriendRequestNotification.
How can I get the function name that triggered the onMessage method of the firebaseMessaging instance? because I want to show different messages for the different cloud functions.
final firebaseMessaging = FirebaseMessaging();
firebaseMessaging.getToken().then((tokenVal) {
fcmToken = tokenVal;
firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
// -----> how can I get the cloudFunctionName ??
if(cloudFunctionName == "sendNewFriendRequestNotification"){
//show friend request notification
}else if(cloudFunctionName == "sendNewChatMessageNotification"){
//show new chat message notification
}
}
You will have to code your function to send that string value in the payload of the message. It will not be sent automatically.

How can I access the Firebase Flutter plugin from a test?

I want to run the real Firebase (not a mock) during a Flutter test. I'm trying to authenticate Firebase with FirebaseOptions:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import "package:test/test.dart";
Future<void> main() async {
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
googleAppID: 'xxxxxxx',
projectID: 'yyyyyy',
),
);
final Firestore firestore = Firestore(app: app);
await firestore.settings(timestampsInSnapshotsEnabled: true);
test('Testing access.', () async {
final FirebaseAuth _auth = FirebaseAuth.instance;
FirebaseUser user = await _auth.signInAnonymously();
firestore.collection('aaaaa').document('bbbbb').get().then((documentSnaphot) {
expect(documentSnaphot['xxxx'], 'ccccc');
});
});
}
However, I'm getting the following error:
Failed to load "C:\Users\Ed\testing\app\test\user_test.dart":
MissingPluginException(No implementation found for method
FirebaseApp#appNamed on channel plugins.flutter.io/firebase_core)
package:flutter/src/services/platform_channel.dart 278:7
MethodChannel.invokeMethod
===== asynchronous gap ===========================
c: 38:53
FirebaseApp.appNamed
===== asynchronous gap ===========================
c: 64:55
FirebaseApp.configure
===== asynchronous gap ===========================
test\usuario_test.dart 7:45
main
===== asynchronous gap ===========================
package:test
serializeSuite
..\..\..\AppData\Local\Temp\flutter_test_listener.64a30b51-eb69-11e8-
a427-1831bf4c06e8\listener.dart 19:27 main
How can I solve this?
Plugins run only on mobile devices (or emulators).
To make testing code that uses plugins possible, you can register your own handlers that respond to method channel requests like the native side of plugins would
test('gets greeting from platform', () async {
const channel = MethodChannel('foo');
channel.setMockMethodCallHandler((MethodCall call) async {
if (call.method == 'bar')
return 'Hello, ${call.arguments}';
throw MissingPluginException();
});
expect(await hello('world'), 'Platform says: Hello, world');
});
From the last section of https://medium.com/flutter-io/flutter-platform-channels-ce7f540a104e
This will check the current firebase user null or not. Still I'm writing the tests. Will update a test for signInAnonymously if possible.
test('API current Firebase user', () async {
MethodChannel channel = MethodChannel(
'plugins.flutter.io/firebase_auth',
);
channel.setMockMethodCallHandler((MethodCall call) async {
if (call.method == 'currentUser') {
return ;
}
;
throw MissingPluginException();
});
FirebaseUser user = await FirebaseAuth.instance.currentUser();
expect(user, null);
});

Resources