Recently I upgraded to firebase_messaging: ^10.0.0 When app is in not running and if notification comes it is displaying twice. I modified the notification data in code after receiving and displayed it. Even then I can see the modified and non modified notifications. I don't know where that notification is triggering. But when app is running it is displaying notification once only(works fine). Here goes my code
/*main.dart*/
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
//await Firebase.initializeApp();
// await HomePageState.handleMessage(message);
String title="${message.notification!.title}";
String body="${message.notification!.body}";
_flutterLocalNotificationsPlugin.show(0, title, body, platformChannelSpecifics, payload: jsonEncode(message.data));
AppDatabase database= await $FloorAppDatabase.databaseBuilder(Constants.dataBaseName).addMigrations([migration1to2]).build();
if(!title.toLowerCase().contains("cancelled")){
var date=DateFormat("dd-MMM-yyyy hh:mm aa").format(DateTime.now());
NotificationModel notification=NotificationModel(title: title,message: body,read: 0,date: date);
await database.notificationDao.insertNotification(notification);
}
print("Handling a background message: ${message.messageId}");
}
Future<void> main() async {
//this line make sure all the required widgets are loaded before main application starts
//SharedPreferences.setMockInitialValues({});
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
var initializationSettingsAndroid = new AndroidInitializationSettings('#mipmap/ic_notification');
var initializationSettingsIOS = new IOSInitializationSettings(onDidReceiveLocalNotification: onDidReceiveLocalNotification);
//var initializationSettings = new InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS);
var initializationSettings = new InitializationSettings(android: initializationSettingsAndroid,iOS: initializationSettingsIOS);
_flutterLocalNotificationsPlugin.initialize(initializationSettings,onSelectNotification: onSelectingNotification);
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
//Initializing repository and carrying forward to avoid multiple instances of database
Repository _repository = Repository();
// runApp(BlocProvider(
// create: (_) => ThemeBloc()..add(ThemeLoadStarted()),
// child: MyApp(
// repository: _repository,
// ),
// ));
SentryOptions options=SentryOptions(dsn: APIs.sentryAPI);
final SentryClient _sentry = SentryClient(options);
Future<void> _reportErrorToSentry(dynamic error, dynamic stackTrace) async {
// Print the exception to the console.
print('Caught error: $error');
await _sentry.captureException(
error,
stackTrace: stackTrace,
);
}
runZonedGuarded(() async{
// runApp(BlocProvider(
// create: (_) => ThemeBloc()..add(ThemeLoadStarted()),
// child: MyApp(
// repository: _repository,
// ),
// ));
await SentryFlutter.init(
(options) => options.dsn = APIs.sentryAPI,
appRunner: () => runApp(BlocProvider(
create: (_) => ThemeBloc()..add(ThemeLoadStarted()),
child: MyApp(
repository: _repository,
),
)),
);
}, (error, stackTrace) {
print('runZonedGuarded: Caught error in my root zone.');
FirebaseCrashlytics.instance.recordError(error, stackTrace);
_reportErrorToSentry(error,stackTrace);
});
}
Application.kt
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingBackgroundService
class Application : FlutterApplication(), PluginRegistrantCallback {
override fun registerWith(registry: PluginRegistry) {
//FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
FlutterLocalNotificationPluginRegistrant.registerWith(registry)
SqflitePluginRegistrant.registerWith(registry)
}
}
FirebaseCloudMessagingPluginRegistrant.kt which I commented out in application.kt
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin
class FirebaseCloudMessagingPluginRegistrant {
companion object {
fun registerWith(registry: PluginRegistry) {
if (alreadyRegisteredWith(registry)) {
return
}
FlutterFirebaseMessagingPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"))
}
fun alreadyRegisteredWith(registry: PluginRegistry): Boolean {
val key = FirebaseCloudMessagingPluginRegistrant::class.java.name
if (registry.hasPlugin(key)) {
return true
}
registry.registrarFor(key)
return false
}
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="**.***.***tomer">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />-->
<!-- android:label="#string/app_label"-->
<application
android:name=".Application"
android:label="Zuzu"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="#xml/network_security_config"
android:icon="#mipmap/ic_launcher">
<meta-data android:name="io.flutter.network-policy"
android:resource="#xml/network_security_config"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
tools:targetApi="n">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="#drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBqZ4aa************GMzHpofWKeEU"/>
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="#string/facebook_app_id"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="Zuzu" />
<meta-data android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#mipmap/ic_notification"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/notification"/>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="#style/Theme.AppCompat.Light.NoActionBar"/>
</application>
</manifest>
This issue is in android only that too when app is not running. Where am I doing wrong?
It appears you're sending a "notification" message from Firebase Messaging when you should be sending a "data" message.
Here is the difference between a notification message and a data message:
Notification message
FCM automatically displays the message to end-user devices on behalf
of the client app.
Data message
Client app is responsible for processing data messages.
About FCM Messages | Firebase (#Message types)
That explains why you get the unmodified version of the notification (this is from the automatic display of the notification message) and the modified version of the notification (this is from your own code where you handle the notification).
Solution:
You need to send a data message from your backend.
So if you had this before as your notification payload:
{
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"notification":{
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
}
}
}
you should change it to:
{
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"data":{
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
}
}
}
Related
I have used FCM along with Flutter local notification for receiving messages. Flutter local notifiation is used to show notification when app is in foreground. Currently, I am using Android 12 (Poco X3 PRO).
When I terminate the application and send a message from Firebase Console, the below message appears in the logcat.
2022-07-26 07:40:16.751 11370-11370/? W/GCM: broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE flg=0x10000000 pkg=au.org.nrna.test.app (has extras) }
I have checked the firebase cloud messaging documentation linked HERE, however there is no mention of message handling in terminated state.
Similarly, when app is in foreground, the message appears in the notification tray only. It does not show up on the screen at all.
FILE: main.dart
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:device_preview/device_preview.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'core/apps/app.dart';
import 'core/apps/device_preview_app.dart';
import 'core/bloc_observers/bloc_observer.dart';
import 'core/config_reader/config_reader.dart';
import 'core/injections/injections.dart';
import 'core/routes/app_router.gr.dart';
final appRouter = AppRouter();
void main() async {
BlocOverrides.runZoned(
() async {
runZonedGuarded<Future<void>>(() async {
initAndRunApp();
},
(error, stack) => FirebaseCrashlytics.instance
.recordError(error, stack, fatal: true));
},
blocObserver: MyBlocObserver(),
);
}
void onDidReceiveLocalNotification(
int id, String? title, String? body, String? payload) {
// display a dialog with the notification details, tap ok to go to another page
debugPrint(
'Notification[#$id] - Title: $title, Body: $body Payload: $payload');
}
void onSelectNotification(String? payload) {
if (payload != null) {
Map<String, dynamic> payloadMap = jsonDecode(payload);
debugPrint(payloadMap.toString());
}
}
Future _setupPushNotification() async {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings androidInitializationSettings =
AndroidInitializationSettings('launch_background');
const IOSInitializationSettings iosInitializationSettings =
IOSInitializationSettings(
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
const InitializationSettings initializationSettings = InitializationSettings(
iOS: iosInitializationSettings,
android: androidInitializationSettings,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
final bool? result = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
var androidDetails = const AndroidNotificationDetails(
'com.test.app.notification_channel',
'Test Notification Channel',
channelDescription: 'Notification Channel for Test App for Android',
priority: Priority.high,
);
var iosDetails = const IOSNotificationDetails();
var generalNotificationDetails =
NotificationDetails(android: androidDetails, iOS: iosDetails);
FirebaseMessaging.onBackgroundMessage(_onBackgroundMessageHandler);
FirebaseMessaging.onMessage.listen((message) {
debugPrint(
'FCM NOTIFICATION[#${message.messageId}] - Data: ${message.data} Title: ${message.notification?.title} Body: ${message.notification?.body}');
RemoteNotification? notification = message.notification;
AndroidNotification? androidNotification = message.notification?.android;
AppleNotification? appleNotification = message.notification?.apple;
String? payloadJsonStr;
if (message.data.isNotEmpty) {
payloadJsonStr = jsonEncode(message.data);
}
if (notification != null && androidNotification != null) {
flutterLocalNotificationsPlugin.show(notification.hashCode,
notification.title, notification.body, generalNotificationDetails,
payload: payloadJsonStr);
}
});
}
Future<void> _onBackgroundMessageHandler(RemoteMessage message) async {
debugPrint(
'Background FCM NOTIFICATION[#${message.messageId}] - Data: ${message.data} Title: ${message.notification?.title} Body: ${message.notification?.body}');
}
Future _initialiseApp() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// setup Push Notifications
_setupPushNotification();
// Pass all uncaught errors from the framework to Crashlytics.
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
// GetIt configuration
configureDependencies();
await getIt<ConfigReader>().initialize();
}
Future initAndRunApp() async {
await _initialiseApp();
Isolate.current.addErrorListener(RawReceivePort((pair) async {
final List<dynamic> errorAndStacktrace = pair;
await FirebaseCrashlytics.instance.recordError(
errorAndStacktrace.first,
errorAndStacktrace.last,
fatal: true,
);
}).sendPort);
final fcmToken = await FirebaseMessaging.instance.getToken();
debugPrint(fcmToken);
if (!kReleaseMode && getIt<ConfigReader>().isDevicePreviewEnabled) {
runApp(
DevicePreview(
enabled: !kReleaseMode,
builder: (context) => const DevicePreviewApp(), // Wrap your app
),
);
} else {
runApp(const MyApp());
}
}
FILE: AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.app">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="My App"
android:name="${applicationName}"
android:icon="#mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
The error seems to occur when running the app in debug mode. If you stop the debugging, it is treated as force-stop or killing the application.
Firebase documentation states that notification will not show on a force-stop or killed app unless the app is opened again.
DOCUMENTATION: Receive messages in a Flutter app. Look for the section titled Receive messages in a Flutter app in the page.
try running your app with release mode using flutter run --release and check
Just created a new Flutter app
Ultimately trying to open my app or show a full screen Activity when I receive a notification (think phone call)
Currently just trying to console log in a native class called through the background message handler
Receiving this error when I send the notification, do you see what I'm doing wrong? Is there a different way I should approach this?
D/FLTFireMsgReceiver(17161): broadcast received for message
W/com.myapp(17161): Accessing hidden method Landroid/os/WorkSource;->add(I)Z (greylist,test-api, reflection, allowed)
W/com.myapp(17161): Accessing hidden method Landroid/os/WorkSource;->add(ILjava/lang/String;)Z (greylist,test-api, reflection, allowed)
W/com.myapp(17161): Accessing hidden method Landroid/os/WorkSource;->get(I)I (greylist, reflection, allowed)
W/com.myapp(17161): Accessing hidden method Landroid/os/WorkSource;->getName(I)Ljava/lang/String; (greylist, reflection, allowed)
I/flutter (17161): FlutterFire Messaging: An error occurred in your background messaging handler:
I/flutter (17161): MissingPluginException(No implementation found for method getBatteryLevel on channel samples.flutter.dev/battery)
main.dart
...
Future<void> backgroundHandler(RemoteMessage message) async {
const platform = const MethodChannel('samples.flutter.dev/battery');
String response = "";
try {
final int result = await platform.invokeMethod('getBatteryLevel');
response = "Response: $result";
} on PlatformException catch (e) {
response = "Failed to Invoke: '${e.message}'.";
}
print(response);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(MyApp());
}
...
MainActivity.kt
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
Log.i(">>BLOOP", "inside configureFlutterEngine")
GeneratedPluginRegistrant.registerWith(flutterEngine);
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// Note: this method is invoked on the main thread
Log.i(">>BLOOP", "call looking for method: ${call.method}");
if(call.method == "getBatteryLevel") {
result.success(1);
} else {
result.notImplemented()
}
}
}
}
AndroidManifest.xml
...
</activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="samples.flutter.dev/battery" />
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Things I've tried:
flutter clean flutter run
Uninstalling/Reinstalling the app on the phone
Reverting Android Gradle Plugin Version to 3.5.1
Adding android/app/src/proguard-rules.pro with -keep class androidx.lifecycle.DefaultLifecycleObserver
Using a different channel than the one I set in the manifest
platform.invokeMethod() from a button click inside the app, it does work from the app UI
I was unable to register a native MethodCall function the background handler could invoke. Instead I found 2 solutions:
Capture the message natively
Use flutter-local-notifications and streams
I ended up going with solution 2 due to better platform support and it being less hacky
I'm only going to show android implementations
(option 1) Capture the message natively
Install firebase-messaging
// android/app/build.gradle
...
dependencies {
...
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.firebase:firebase-messaging:21.1.0' <-- THIS ONE
}
Create a background service
// android/app/src/main/kotlin/<project>/BackgroundService.kt
class BackgroundService: FirebaseMessagingService() {
// Function triggered when we receive a message (as specified in AndroidManifest.xml
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
// Waking the screen for 5 seconds and disabling keyguard
val km = baseContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
val kl = km.newKeyguardLock("MyKeyguardLock")
kl.disableKeyguard()
val pm = baseContext.getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE, TAG)
wakeLock.acquire(5000L)
// Create an intent to launch .MainActivity and start it as a NEW_TASK
val notificationIntent = Intent("android.intent.category.LAUNCHER")
notificationIntent
.setAction(Intent.ACTION_MAIN)
.setClassName("com.myapp", "com.myapp.MainActivity")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(notificationIntent)
}
}
Register permissions and the BackgroundService to receive firebase message events
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp">
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
...
</activity>
<service android:name=".BackgroundService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
(option 2) Use flutter-local-notifications and streams
Install flutter deps
# pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
firebase_core: ^1.0.4
firebase_messaging: ^9.1.2
flutter_local_notifications: ^5.0.0+3
rxdart: ^0.26.0
...
Register permissions
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
...
<activity
...
android:showWhenLocked="true"
android:turnScreenOn="true">
Top level initialization in main.dart
// We use this to `show` new notifications from the app (instead of from firebase)
// You'll want to be sending high priority `data` messages through fcm, not notifications
final FlutterLocalNotificationsPlugin localNotifications = FlutterLocalNotificationsPlugin();
// Uses rxdart/subjects to trigger showing a different page in your app from an async handler
final BehaviorSubject<String> selectNotificationSubject = BehaviorSubject<String>();
FCM message handler in main.dart
// handler function for when we receive a fcm message
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// build out and show notification using flutter-local-notifications
const AndroidNotificationDetails androidSpecifics = AndroidNotificationDetails(
"channel_id", "channel_name", "Test bed for all dem notifications",
importance: Importance.max, priority: Priority.max, fullScreenIntent: true, showWhen: true);
const NotificationDetails platformSpecifics = NotificationDetails(android: androidSpecifics);
// The notification id should be unique here
await localNotifications.show(DateTime.now().second, "Plain title", "Plain body", platformSpecifics,
payload: "item x");
}
entrypoint function in main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// if the notification launched the app then show alert page
final NotificationAppLaunchDetails launchDetails = await localNotifications.getNotificationAppLaunchDetails();
String initialRoute = launchDetails.didNotificationLaunchApp ?? false ? ShowAlert.routeName : Home.routeName;
// initialize `flutter-local-notifications`, app_icon needs to be added as a drawable resource
const AndroidInitializationSettings androidSettings = AndroidInitializationSettings("app_icon");
final InitializationSettings initializationSettings = InitializationSettings(android: androidSettings);
await localNotifications.initialize(initializationSettings, onSelectNotification: (String payload) async {
// Will be called when activity is shown from notification, here we can trigger showing our alert page
initialRoute = ShowAlert.routeName;
selectNotificationSubject.add(payload);
});
// Catch firebase messages with our handler function, which will then build and show a notification
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
runApp(
MaterialApp(
initialRoute: initialRoute,
routes: <String, WidgetBuilder>{
Home.routeName: (_) => Home(),
ShowAlert.routeName: (_) => ShowAlert(),
},
),
);
}
Home widget in main.dart
// Needs to be stateful so we can use lifecycle methods for the notification stream changes and navigation
class Home extends StatefulWidget {
static const String routeName = "/home";
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
super.initState();
// when onSelectNotification is called, our new payload will trigger this function
selectNotificationSubject.stream.listen((String payload) async {
await Navigator.pushNamed(context, ShowAlert.routeName);
});
}
#override
void dispose() {
selectNotificationSubject.close();
super.dispose();
}
...
I've used react-native-fcm for remote notification in android and iPhone.
react-native-fcm
In Android foreground I'm not be able to getting remote notification in notification bar.
In background mode I'm able to getting notification successfully but some how in foreground doesn't.
Android Manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nusape">
<application>
<receiver android:name="com.evollu.react.fcm.FIRLocalMessagingPublisher"/>
<receiver android:enabled="true" android:exported="true" android:name="com.evollu.react.fcm.FIRSystemBootEventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="#mipmap/ic_launcher"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="my_default_channel"/>
<service android:name="com.evollu.react.fcm.MessagingService" android:enabled="true" android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<service android:name="com.evollu.react.fcm.InstanceIdService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<activity android:launchMode="singleTop" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="fcm.ACTION.HELLO" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
App.js
async componentDidMount() {
// create NotificationChannel for future use!
FCM.createNotificationChannel({
id: 'my_default_channel',
name: 'Default',
description: 'used for example',
priority: 'high'
});
// initially user get InitialNotification frim the app if any pending
FCM.getInitialNotification().then(notif => {
console.log("getInitialNotification Notification : => ", notif);
// if notif.targetScreen is details screen then it will redirect to details screen directly!
if (notif && notif.targetScreen === "detail") {
setTimeout(() => {
this.props.navigation.navigate("Detail");
}, 500);
}
});
// added notification listener for getting any notification called below function then
this.notificationListener = FCM.on(FCMEvent.Notification, async (notif) => {
console.log("FCMEvent.Notification Notification : => ", notif);
if (Platform.OS === 'ios' && notif._notificationType === NotificationType.WillPresent && !notif.local_notification) {
notif.finish(WillPresentNotificationResult.All);
return;
}
// if user tap to notification bar then open app then below condition will follow up and redirect to details screen!
if (notif.opened_from_tray) {
if (notif.targetScreen === 'detail') {
setTimeout(() => {
navigation.navigate('Detail')
}, 500)
}
setTimeout(() => {
alert(`User tapped notification\n${JSON.stringify(notif)}`)
}, 500)
}
// check whether app is in background or foreground for generate notification
if (AppState.currentState !== 'background'){
this.showLocalNotification(notif);
});
// getting user permission for sending notification or not ?
try {
let result = await FCM.requestPermissions({
badge: true,
sound: true,
alert: true
});
console.log("Notification requestPermissions : => ", result)
} catch (e) {
console.error(e);
}
// Generating token for particular user wise send notification
FCM.getFCMToken().then(token => {
FCM.subscribeToTopic("channelToTopic");
console.log("Notification token : => ", token);
this.setState({ token: token || "" });
});
// Get APNSTOKEN for only ios
if (Platform.OS === "ios") {
FCM.getAPNSToken().then(token => {
console.log("APNS TOKEN (getFCMToken)", token);
});
}
}
// show notification when app is in foreground and getting any new notification
showLocalNotification = (notif) => {
FCM.presentLocalNotification({
channel: 'my_default_channel',
id: new Date().valueOf().toString(),
title: notif.fcm.title,
body: notif.fcm.body,
priority: "high",
badge: 1,
number: 1,
ticker: "My Notification Ticker",
auto_cancel: true,
big_text: "Show when notification is expanded",
sub_text: "This is a subText",
wake_screen: true,
group: "group",
icon: "ic_launcher",
ongoing: true,
my_custom_data: "my_custom_field_value",
lights: true,
show_in_foreground: true
});
};
I'm suffering this issue from last 2 months and not get it well solution for the same as i doing so many new attempt to resolve issue but at the end not getting any succeed.
According to the official Github of
react-native-fcm,
this library is depreciated.
You can use the
react-native-firebase
for generating notification.
I was able to get the notifications working in about 2 hours for android.
If you want the code I can share it.
good luck.
Update - Sorry I couldn't answer earlier because of my office account.
This is my code for showing android foreground notifications.
firebase.messaging()
.subscribeToTopic(this.state.user.user_name)
.then(response => console.log('response from FCM TOPIC' + response))
.catch(error => console.log('error from FCM TOPIC'+ error));
this.notificationListener = firebase.notifications().onNotification(notification => {
let notificationMessage = notification._android._notification._data.action;
let recordId = notification._android._notification._data.recordID;
let { title, body } = notification;
// console.log('ttttt', notification)
// notification.android.setAutoCancel(false)
console.log(title, body, notificationMessage, recordId);
const channelId = new firebase.notifications.Android.Channel(
'Default',
'Default',
firebase.notifications.Android.Importance.High
);
firebase.notifications().android.createChannel(channelId);
let notification_to_be_displayed = new firebase.notifications.Notification({
data: notification._android._notification._data,
sound: 'default',
show_in_foreground: true,
title: notification.title,
body: notification.body,
});
if (Platform.OS == 'android') {
notification_to_be_displayed.android
.setPriority(firebase.notifications.Android.Priority.High)
.android.setChannelId('Default')
.android.setVibrate(1000);
}
console.log('FOREGROUND NOTIFICATION LISTENER: \n', notification_to_be_displayed);
firebase.notifications().displayNotification(notification_to_be_displayed);
});
As per the library issues listed here you can try two things:
just pass show_in_foreground in your data property in remote notification
android shows notification only when app state is killed or background. To display notifications in app foreground, you need to show local notification.
Sample code:
FCM.on(FCMEvent.Notification, notif => {
if (!notif.opened_from_tray) {
showLocalNotification();
}
});
showLocalNotification() {
FCM.presentLocalNotification({
id: new Date().valueOf().toString(), // (optional for instant notification)
title: "Test Notification with action", // as FCM payload
body: "Force touch to reply", // as FCM payload (required)
show_in_foreground: true // notification when app is in foreground (local & remote)
});
}
Full code is here
Which API level you are testing on ? Android API 26 and above requires channels to be created in order to receive notifications in foreground. please read this for more information.
react-native-fcm is also updated to include channels too, refer this
though the library should not be used anymore as the library is not maintained anymore, a good alternative is react-native-firebase.
Im using react-native-firebase in my app for cloud messanging + notification. it get all body and data when app is in use but it cant get anything when app is closed or in background ....
I tried Headless JS but thats also not working
when i click on notification and when it open's the app its shows this {"google.priority":"high"}
thnaks in advance....
this is my android mainfest
<application
android:name=".MainApplication"
android:label="#string/app_name"
android:icon="#mipmap/ic_launcher"
android:allowBackup="false"
android:theme="#style/AppTheme"
android:largeHeap="true">
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_stat_ic_notification" />
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!---firebase -->
<service android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- <meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/default_notification_channel_id"/> -->
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />
<!---firebase end-->
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
this is my componentdidmount() function
async componentDidMount() {
this.getValueLocally();
requestCameraLOCATION()
const notificationOpen: NotificationOpen = await firebase.notifications().getInitialNotification();
if (notificationOpen) {
const action = notificationOpen.action;
const notification: Notification = notificationOpen.notification;
var seen = [];
alert(JSON.stringify(notification.data, function(key, val) {
if (val != null && typeof val == "object") {
if (seen.indexOf(val) >= 0) {
return;
}
seen.push(val);
}
return val;
}));
}
const channel = new firebase.notifications.Android.Channel('test-channel', 'Test Channel', firebase.notifications.Android.Importance.Max)
.setDescription('My apps test channel');
// Create the channel
firebase.notifications().android.createChannel(channel);
this.notificationDisplayedListener = firebase.notifications().onNotificationDisplayed((notification: Notification) => {
// Process your notification as required
// ANDROID: Remote notifications do not contain the channel ID. You will have to specify this manually if you'd like to re-display the notification.
});
this.notificationListener = firebase.notifications().onNotification((notification: Notification) => {
// Process your notification as required
notification
.android.setChannelId('test-channel')
.android.setSmallIcon('ic_launcher');
firebase.notifications()
.displayNotification(notification);
});
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
// Get the action triggered by the notification being opened
const action = notificationOpen.action;
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification;
var seen = [];
alert(JSON.stringify(notification.data, function(key, val) {
if (val != null && typeof val == "object") {
if (seen.indexOf(val) >= 0) {
return;
}
seen.push(val);
}
return val;
}));
firebase.notifications().removeDeliveredNotification(notification.notificationId);
});
}
async checkPermission() {
firebase.messaging().hasPermission()
.then(enabled => {
if (enabled) {
this.getToken();
} else {
this.requestPermission();
}
});}
async getToken() {
let fcmToken = await AsyncStorage.getItem('fcmToken');
if (!fcmToken) {
fcmToken = await firebase.messaging().getToken();
if (fcmToken) {
// user has a device token
await AsyncStorage.setItem('fcmToken', fcmToken);
}
}
}
async requestPermission() {
firebase.messaging().requestPermission()
.then(() => {
this.getToken();
})
.catch(error => {
console.warn(error);
});
}
and this is my bgMessaging.js
import firebase from 'react-native-firebase';
import type { RemoteMessage } from 'react-native-firebase';
import type { Notification,NotificationOpen} from 'react-native-firebase';
export default async (message: RemoteMessage) => {
const newNotification = new firebase.notifications.Notification()
.android.setChannelId(message.data.channelId)
.setNotificationId(message.messageId)
.setTitle(message.data.title)
.setBody(message.data.body)
.setSound("default")
.setData(message.Data)
.android.setAutoCancel(true)
.android.setSmallIcon('ic_notification')
.android.setCategory(firebase.notifications.Android.Category.Alarm)
// Build a channel
const channelId = new firebase.notifications.Android.Channel(message.data.channelId, channelName, firebase.notifications.Android.Importance.Max);
// Create the channel
firebase.notifications().android.createChannel(channelId);
firebase.notifications().displayNotification(newNotification)
return Promise.resolve();
}
there are two types of messages:
notification + data messages that will be handled by the FCM while in the background (so your code will not have access to the notification), and will call onNotification while in the foreground,
data-only messages will call headlessjs while in the background/closed and will call onMessage while in the foreground.
note: if you remove the title and body, your message will be categorized as the second one. also, data is optional in the first category.
to recap:
for data-only messages:
App in foreground : onMessage triggered
App in background/App closed : Background Handler (HeadlessJS)
for notification + data messages:
App in foreground : onNotification triggered
App in background/App closed : onNotificationOpened triggered if the notification is tapped
for more information and for iOS read the official docs for data-only messages and notification+data messages
It seems that RN firebase did not support this functionality on Android.
Please refer below:
https://rnfirebase.io/docs/v5.x.x/notifications/receiving-notifications#4)-Listen-for-a-Notification-being-opened
On Android, unfortunately there is no way to access the title and body of an opened remote notification. You can use the data part of the remote notification to supply this information if it's required.
I'm trying to show push notification when app is closed so I'm setting up the push notification with react-native-firebase with this docs
I set AndroidManifest.xml as described in docs
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.moonsite.sqlivetrivia"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:label="#string/app_name"
android:icon="#mipmap/ic_launcher"
android:theme="#style/AppTheme">
<receiver android:name="io.invertase.firebase.notifications.RNFirebaseNotificationReceiver"/>
<receiver android:enabled="true" android:exported="true" android:name="io.invertase.firebase.notifications.RNFirebaseNotificationsRebootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_stat_ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorAccent" />
<!--
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/default_notification_channel_id"/> -->
<service android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop"
>
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
then I wrote the code in react native
import React, { Component } from 'react'
import { View } from 'react-native'
import { Input, Text, Button } from '../Components'
import type { RemoteMessage } from 'react-native-firebase'
import firebase from 'react-native-firebase'
import type { Notification, NotificationOpen } from 'react-native-firebase';
export default class TestComponent extends Component {
async componentDidMount() {
await this.SetUpAuth();
await this.SetUpMessaging();
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
// Get the action triggered by the notification being opened
const action = notificationOpen.action;
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification;
});
const notificationOpen: NotificationOpen = await firebase.notifications().getInitialNotification();
if (notificationOpen) {
console.log(notificationOpen)
// App was opened by a notification
// Get the action triggered by the notification being opened
const action = notificationOpen.action;
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification;
}
}
componentWillUnmount() {
}
async SetUpAuth() {
const credential = await firebase.auth().signInAnonymouslyAndRetrieveData();
if (credential) {
console.log('default app user ->', credential.user.toJSON());
} else {
console.error('no credential');
}
}
async SetUpMessaging() {
this.notification2 = new firebase.notifications.Notification()
.setNotificationId('notificationId')
.setTitle('My notification title')
.setBody('My notification body')
.android.setChannelId('test')
.android.setClickAction('action')
.setData({
key1: 'value1',
key2: 'value2',
});
this.notification2
.android.setChannelId('channelId')
.android.setSmallIcon('ic_launcher');
console.log('assa')
onTokenRefreshListener = firebase.messaging().onTokenRefresh(fcmToken => {
console.log('token generated ->', fcmToken);
// store.dispatch(DeviceActions.SetFCMToken(fcmToken));
});
const fcmToken = await firebase.messaging().getToken();
if (fcmToken) {
// user has a device token
console.log('has token ->', fcmToken);
console.log(firebase.auth().currentUser._user)
firebase.database().ref(`/users/${firebase.auth().currentUser._user.uid}`).set({ pushToken: fcmToken })
// store.dispatch(DeviceActions.SetFCMToken(fcmToken));
} else {
// user doesn't have a device token yet
console.error('no messaging token');
}
const messagingEnabled = await firebase.messaging().hasPermission();
if (messagingEnabled) {
// user has permissions
console.log('User has FCM permissions');
} else {
// user doesn't have permission
console.log('User does not have FCM permissions');
await this.RequestMessagePermissions();
}
messageListener = firebase.messaging().onMessage((message: RemoteMessage) => {
console.log(`Recieved message - ${JSON.stringify(message)}`);
});
notificationDisplayedListener = firebase
.notifications()
.onNotificationDisplayed(notification => {
// Process your notification as required
// ANDROID: Remote notifications do not contain the channel ID. You will have to specify this manually if you'd like to re-display the notification.
console.log(`Recieved notification 1`);
});
notificationListener = firebase
.notifications()
.onNotification(notification => {
console.log(notification)
firebase.notifications().displayNotification(this.notification2)
// Process your notification as required
console.log(`Recieved notification 2`);
});
}
async RequestMessagePermissions() {
console.log('request')
console.log('Requesting FCM permission');
await firebase
.messaging()
.requestPermission()
.catch(err => console.err(err));
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
</View>
)
}
}
then I created firebase http function just for test
export const sendNotification = functions.https
.onRequest(async (req, res) => {
try {
const { token } = req.query;
let tokens = 'fyTfgNZAUBA:APA91bGnx9Vp9MwRA4ohVKcNOM8d5s4a4TXdtI0KTSzcWEgZX1WoLGpofcVQFTCdSbnbqObkukzJEXF3cbmvENYD5pr2MrukjqUNy_bclDuM3rJsV5iSU1vWywL2ZVijJ3s0E9GNfRRe'
var payload = {
notification: {
title: 'Urgent action needed!',
body: 'Urgent action is needed to prevent your account from being disabled!',
sound: 'default',
color: '#84B2D9',
icon: 'ic_notification',
click_action: 'OPEN_MAIN_ACTIVITY'
}
};
// Set the message as high priority and have it expire after 24 hours.
var options = {
priority: 'high',
timeToLive: 60 * 60 * 24
};
console.log('token',token)
let d = await admin.messaging().sendToDevice(tokens, payload,options);
return res.status(200).send({ success: d})
} catch (e) {
console.info(e)
return res.status(400).send({ error: 0 })
}
})
in the response from http function I get success(as you can see here)
{
"success": {
"results": [
{
"messageId": "0:1525112032423298%a0cec506a0cec506"
}
],
"canonicalRegistrationTokenCount": 0,
"failureCount": 0,
"successCount": 1,
"multicastId": 5934848553304349000
}
}
and in my debugger in console log I get the notification as I want
but the issue I don't get push notification when app is closed. I want to get any notification when I'm not into the app.
in addition when I use to display the notification when I get any notification
notificationListener = firebase
.notifications()
.onNotification(notification => {
console.log(notification)
firebase.notifications().displayNotification(this.notification2)
// Process your notification as required
console.log(`Recieved notification 2`);
});
}
so it not display my details here
this.notification2 = new firebase.notifications.Notification()
.setNotificationId('notificationId')
.setTitle('My notification title')
.setBody('My notification body')
.android.setChannelId('test')
.android.setClickAction('action')
.setData({
key1: 'value1',
key2: 'value2',
});
so do i need to save it on state and then in title to display the data?