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
Related
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"
}
}
}
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 am new to flutter and don't know much about android. I would like to have my flutter app allow users to launch ADM with download data to download files.Now I have tried different approaches but did seem to work. I have tried url_launcher which didn't launch
ListTile(
title: Text("ADM LAUNCH"),
onTap: () async {
String adm = "com.dv.adm";
if (await canLaunch(adm)) {
await launch(adm);
} else {
throw ("can't launch ");
}
},
),
I tried android intent as well:
ListTile(
title: Text("android intent"),
onTap: () async {
final AndroidIntent intent = AndroidIntent(
action: 'action_view',
package: 'com.dv.adm',
);
await intent.launch();
},
),
How can I start ADM from my app and pass in download argument ? also what kind of argument do I have to pass ? like link or url ?
found the way to do it:
packages: android intent, device apps
first add the activity to AndroidManifest.xml in application tag:
<!-- Android ADM intent -->
<activity android:name="com.dv.adm"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN">
</action>
</intent-filter>
</activity>
<!-- END OF ADM INTENT -->
flutter code:
import 'package:device_apps/device_apps.dart';
import 'package:android_intent/android_intent.dart';
download(String url, String fileName) async {
bool isInstalled = await DeviceApps.isAppInstalled('com.dv.adm');
if (isInstalled) {
final AndroidIntent intent = AndroidIntent(
action: 'action_main',
package: 'com.dv.adm',
componentName: 'com.dv.adm.AEditor',
arguments: <String, dynamic>{
'android.intent.extra.TEXT': url,
'com.android.extra.filename': "$fileName.mp4",
},
);
await intent.launch().then((value) => null).catchError((e) => print(e));
} else {
// ask user to install the app
}
}
This will open ADM editor with the arguments u provide.
I need to show firebase notifications when the app is on foreground by using local notification but it is not working.
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin=new FlutterLocalNotificationsPlugin();
static FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
static StreamController<Map<String, dynamic>> _onMessageStreamController =
StreamController.broadcast();
static StreamController<Map<String, dynamic>> _streamController =
StreamController.broadcast();
static final Stream<Map<String, dynamic>> onFcmMessage =
_streamController.stream;
#override
void initState() {
super.initState();
var android=AndroidInitializationSettings('mipmap/ic_launcher.png');
var ios=IOSInitializationSettings();
var platform=new InitializationSettings(android,ios);
flutterLocalNotificationsPlugin.initialize(platform);
firebaseCloudMessaging_Listeners();
}
Here is the Firebase Code
void firebaseCloudMessaging_Listeners() {
if (Platform.isIOS) iOS_Permission();
_firebaseMessaging.getToken().then((token) {
print("FCM TOKEN--" + token);
});
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('on message $message');
showNotification(message);
},
onResume: (Map<String, dynamic> message) async {
print('on resume $message');
},
onLaunch: (Map<String, dynamic> message) async {
print('on launch $message');
},
);
}
This is showNotification method
void showNotification(Map<String, dynamic> msg) async{
print(msg);
var android = new AndroidNotificationDetails(
'my_package', 'my_organization', 'notification_channel', importance: Importance.Max, priority: Priority.High);
var iOS = new IOSNotificationDetails();
var platform=new NotificationDetails(android, iOS);
await flutterLocalNotificationsPlugin.show(
0,'My title', 'This is my custom Notification', platform,);
}
and Firebase Response
{notification: {title: Test Title, body: Test Notification Text}, data: {orderid: 2, click_action: FLUTTER_NOTIFICATION_CLICK, order name: farhana}}
You can find the answer in FlutterFire documentation
https://firebase.flutter.dev/docs/migration/#messaging
You just add to your code the following line
FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
There is an active issue logged on GitHub repository for the package regarding the same. Firebase messaging and local notifications won't work together on iOS since you can register only a single delegate for notifications.
Check out: https://github.com/MaikuB/flutter_local_notifications/issues/111
There's also an active flutter issue for the same:
https://github.com/flutter/flutter/issues/22099
Problem:
See the Push Notification while application is in foreground.
Solution:
I was using firebase_message plugin and I was able to see the Push Notification while application is in foreground by making these few changes in my flutter project's iOS AppDelegate.swift file in flutter project.
import UIKit
import Flutter
import UserNotifications
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate, UNUserNotificationCenterDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// set the delegate in didFinishLaunchingWithOptions
UNUserNotificationCenter.current().delegate = self
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// This method will be called when app received push notifications in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void)
{
completionHandler([.alert, .badge, .sound])
}
}
Note:
This also works while using with flutter_local_notification plugin but with an issue that onSelectNotification is not working due to changes done above.
For not receiving the notification in foreground make sure the android drawable file contains the launcher_icon or the icon which you have set in shownotification function.
I also couldn't get firebase notifications with flutter_local_notifications working on iOS in foreground. Background notifications worked fine. Problem was that firebase notification data is different on iOS. Notification data is not ["notification"]["title"] as in android but ["aps"]["alert"]["title"].
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.