I'm trying to navigate user when user tap the notification.
It is working as I want but one situation(application is not opened), I mean application is not on the background, it is completely closed. User tap the notification and application firstly see the splash sceren after that load some initial news on initial page and do not navigating. I tried to add some delay but not worked. How can I fix this?
My main function
main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
setupLocator();
await Hive.initFlutter();
Hive.registerAdapter(UserDataAdapter());
await Hive.openBox<UserData>('userData');
await dotenv.load();
await GetStorage.init();
Get.put(MainController());
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
runApp(const MyApp());
}
Running notification tap function when user click the notification
Future<void> onTapNotification(RemoteMessage message) async {
log('Notification tapped', name: 'TAP NOTIFICATION');
inspect(message);
Get.toNamed(
'/notification-news',
parameters: {
"news_id": message.data['news_id'],
},
);
}
Related
After carefully trying other answer, nothing seems to work.
Notification are working fine, when I click on them and the app has been initialised.
When the app is closed or terminated it is impossible for me to redirect the user to a page because the value of the getInitialMessage() is null.
For example, this return null, but it is the home page of my Flutter app
#override
void initState() {
super.initState();
FirebaseMessaging.instance
.getInitialMessage()
.then((message) => print('Initial message: $message'))
Main.dart
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
runApp(const FrontDeskApp());
}
Thank you
This may be the reason because you are using getInitialMessage() method in wrong way.
Modify your main.dart like the below way and test.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
await FirebaseMessaging.instance.getInitialMessage().then((remoteMessage) {
// whenever you open the app from the terminate state by clicking on Notification message,
here you will get your remoteMessage.
// Set you App Screen Redirection here.
// Once consumed, the RemoteMessage will be removed.
});
runApp(const MyApp());
}
For more reference please refer to the official document for Handling Interaction of Notification
I am making an app with flutter. I want to store data after 24 hours and update UI in app.
I try with Timer.periodic() but it does not count the time when app is close. It only works when the application is open.
Is it possible to execute a function after a specific time even if the app is closed?
Here is my current code:
void callbackDispatcher() async{
Workmanager().executeTask((task, inputData) {
switch(sdDaily){
case 'StoreDataDaily':
storeData.storeDailyData();
break;
default:
}
return Future.value(true);
});
}
void main() async{
WidgetsFlutterBinding.ensureInitialized();
Directory directory = await path_provider.getApplicationDocumentsDirectory();
print(directory.path);
Hive.init(directory.path);
await Hive.initFlutter(directory.path);
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(WaterAdapter());
Hive.registerAdapter(WeekAdapter());
Get.put(UserController());
Get.put(WaterController());
await Hive.openBox<User>('data');
await Hive.openBox<Water>('water_data');
await Hive.openBox<Week>('week_data');
await notificationPlugin.showNotification();
await Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
var uniqueId = DateTime.now().second.toString();
var userBox = Hive.box<User>('data');
if(userBox.get(0)?.status == 1){
await Workmanager().registerOneOffTask(uniqueId, sdDaily,);
}
runApp(const MyApp());
}
You can use : flutter_background_service. to execute background services and it'll also help you sending a custom notification when you are actually going to store that data later.
You can use firebase cloud funcitons to do schedule tasks or whatever you want to do even if app is closed or killed.
Background
I have a gaming app, and I use Firebase Cloud Messaging (FCM) to send notifications to players when other players do certain things that may concern them. If a player taps the notification, I want them to be navigated to where the action happens, whether the app is currently in foreground, background or terminated. The information about where to navigate is sent with the FCM message.
The default mode for FCM is that when the app is terminated or in background, a remote notification is sent determined by the Cloud Function, but when the app is in foreground, no notification is shown. Instead, the remote message is handled in the app by a streamlistener, in which you can chose to show a local notification, which I do.
But what happens is that if the app is terminated or in background, I seem to get BOTH the remote notification and the local notification! And the biggest problem with that is that if I click the local notification while the app is terminated, I don't get navigated to where I'm supposed to be!...
The Problem
Navigation works from terminated IF I tap the remote notification. Then, the FirebaseMessaging.instance.getInitialMessage() is fired with the correct FCM message.
It also works if app is in foreground or background and I tap the local notification. Then, the onSelectNotification() of the local notification package is fired, with the FCM message as the payload.
But if I tap the local notification when the app is terminated, getInitialMessage() is fired with the initial message as null*, and onSelectNotification() is not fired at all!... Thus, the app cannot find the right information needed for navigation.
What am I doing wrong, or how can I solve this?
My main:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:navigation_history_observer/navigation_history_observer.dart';
import 'local_notifications.dart';
import 'fcm.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'my_firebase.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
MyFirebase.myFutureFirebaseApp = Firebase.initializeApp();
initializeFcm('');
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
LocalNotifications.initiate();
runApp(Blackbox());
}
My initializeFcm:
import 'package:blackbox/local_notifications.dart';
import 'package:blackbox/my_firebase.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
void initializeFcm(String token) async {
await MyFirebase.myFutureFirebaseApp;
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
_firebaseMessaging.getInitialMessage().then((value) {
RemoteMessage? msg = value;
if (msg != null) openAction(msg); // This is where it navigates to the action
});
if (MyFirebase.authObject.currentUser != null) {
// An iOS thing... but can return a Future<NotificationSettings> on Android:
_firebaseMessaging.requestPermission(
sound: true,
//The rest as default
);
// I have no idea what the below does!
_firebaseMessaging.setAutoInitEnabled(true);
// If a message comes in while app in foreground:
FirebaseMessaging.onMessage.listen((remoteMsg) async {
if (remoteMsg.data.isNotEmpty) {
LocalNotifications.showNotification(
title: title,
notification: notification,
data: data,
category: 'GameHub',
description: 'New game hub events',
);
}
}, onError: (error) {
print('Error in onMessage: $error');
});
// Fired if notification opened app from background, not from terminated:
FirebaseMessaging.onMessageOpenedApp.listen((remoteMsg) {
openAction(remoteMsg);
});
}
}
My firebaseMessagingBackgroundHandler:
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage remoteMsg) async {
await Firebase.initializeApp();
if (remoteMsg.data.isNotEmpty) {
if (lots_of_nested_conditions) {
LocalNotifications.showNotification(
title: "Someone is playing your setup!",
notification: "Someone is playing your setup no ${remoteMsg.data['i']}.",
data: jsonEncode(remoteMsg.data),
category: 'GameHub',
description: 'New game hub events',
);
} else {
// If I am neither the player nor the sender of the setup:
print('A background data msg has come in. No local notification. Only maybe Cloud notification.');
}
}
}
My LocalNotifications.initiate(), featuring my onSelectNotification:
class LocalNotifications {
static FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static void initiate() async {
var android = AndroidInitializationSettings('#drawable/ic_stat_name');
var iOS = IOSInitializationSettings();
var initSettings = InitializationSettings(android: android, iOS: iOS);
Future onSelectNotification(String? payload) async {
Map<String, dynamic>? msgData = jsonDecode(payload);
await MyFirebase.myFutureFirebaseApp;
if (MyFirebase.authObject.currentUser != null) {
navigateFromNotificationToFollowing(msgData: msgData);
}
}
flutterLocalNotificationsPlugin.initialize(initSettings, onSelectNotification: onSelectNotification);
}
Edit:
I "solved" this by not sending any local notification in the .onBackgroundMessage. Therefore, I can't accidentally click this notification, and get stuck on not navigating.
However, I'm not entirely happy with this solution! What if I WANT to send a custom local notification when app is terminated, and then be able to click it and be navigated accordingly?
Now I'm wondering if this is because I don't use the "payload" property of the LocalNotification properly...? Maybe that's what's null? 🤷♀️
I assumed I followed all the steps to handling background notifications from firebase in flutter. I have created a top-level function that I am expecting to be triggered whenever a notification comes in. However, the function is never triggered.
Here's the top-level background handler function that exists in my home page widget but outside the class:
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// ignore: avoid_print
print('A background message just showed up : ${message.messageId}');
// update SQLite
var result = await PageService.instance
.add(PageService.instance.convertToPage(message.data));
print('added to db: ${result}');
}
Here is my home page init state that calls a function to initialize firebase messgaging:
#override
void initState() {
super.initState();
_initializeFirebaseMessaging();
}
And then here is the _initializeFirebaseMessaging function that is defined in the home page class as well:
void _initializeFirebaseMessaging() {
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
print('new notification arrived');
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
// update SQLite
var result = await PageService.instance
.add(PageService.instance.convertToPage(message.data));
print('added to db: ${result}');
if (notification != null && android != null) {
// show notification
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
color: Colors.blue,
playSound: true,
icon: '#mipmap/ic_launcher',
),
));
}
});
}
The onmessage.listen works fine as I get notificationd and handle them while I'm in the app, but the background handler is not triggered at all.
I would appreciate any help!
You have to call FirebaseMessaging.onBackgroundMessage inside your main() not in initState()
try: after main func
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
...
}
and remove WidgetsFlutterBinding.ensureInitialized(); in firebaseMessagingBackgroundHandler
onMessage handles foreground notification
onMessageOpenedApp handles background notification
Instead of using onMessage for handling Background notifications, add another function block of FirebaseMessaging with onMessageOpenedApp, if you want notification while the app is in background, like this:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message){...})
onMessage only works when the app is in foreground.
If you want notification while the app is in the terminated state you can use:
FirebaseMessaging.instance.getInitialMessage().then((message){...})
Update your firebaseMessagingBackgroundHandler to this:
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
// ignore: avoid_print
print('A background message just showed up : ${message.messageId}');
// update SQLite
var result = await PageService.instance
.add(PageService.instance.convertToPage(message.data));
print('added to db: ${result}');
}
And finally, the main() should look like this:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Add Firebase Initialization:
await Firebase.initializeApp();
// This will initialize a new Firebase instance.
// Background message handler
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
runApp(const App());
}
You should call FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); from within main(), not from Homepage initState(){}.
I have implemented this method so that when a user clicks the dynamic link it will be redirected to a specific page. Everything works alright while the app is running, but when I kill/close the app and try to do the same thing, it opens the app on the initial screen (Home Page). How can I make it work in this case?
Future<void> initDynamicLinks() async {
FirebaseDynamicLinks.instance.onLink.listen((dynamicLinkData) {
id = dynamicLinkData.link
.toString()
.substring(dynamicLinkData.link.toString().lastIndexOf('/') + 1);
Get.to(
() => Page(
id: id,
),
);
}).onError((error) {
if (kDebugMode) {
print(error.message);
}
});
}
void initState() {
// TODO: implement initState
initDynamicLinks();
super.initState();
}
I think .onLink.listen() function only get hit when app is resumed from background.
If you want your deeplink work when app have a fresh start then just put this code above .onLink.listen() function...
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
// Here you should navigate to your desired screen
Hope it helps you