Im trying to work with firebase messaging on flutter, and here's the situation. I have push notification implemented simply with the following:
void main() async {
WidgetsFlutterBinding();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
}
});
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// await _configureFirebaseAuth();
// await _configureFirebaseStorage();
// _configureFirebaseFirestore();
runApp(const MyApp());
}
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print("Handling a background message: ${message.messageId}");
}
This allows the push notifications to appear, but there is no real functionality to it. In my case, i would like to call navigator and push a certain route. Since this requires context, I placed the following method in my home page(the first screen actually built in the app):
#override
void initState() {
// TODO: implement initState
handleMessageOnBackground();
super.initState();
}
void handleMessageOnBackground() {
FirebaseMessaging.instance.getInitialMessage().then(
(remoteMessage) {
if (remoteMessage != null) {
print('Message: ${remoteMessage.data}');
RemoteMessage? message = remoteMessage;
}
},
);
}
The idea is that, in the initstate method, i now have context, so I would be able to Navigate to a different page. While this works(to my understanding) when the app is completely closed, it will not cover the cases when remote notifications are caught by the .onMessage or the .onBackgroundMessage. Is there any way to pass NotificationMessages from the top of my app down into the main page to use for navigation?
Thank you!
If you want to navigate without context use a GlobalKey.
Define a Globalkey //not inside any class
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
and inside your MaterialAppWidget()
MaterialApp(
navigatorKey: navigatorKey,
...
);
From firebase you'll receive some data in _firebaseMessagingBackgroundHandler(){}
based on that data you can navigate to any screen.
//if you are creating a notification using that data then put required data in payload of that notification. so you can receive that payload when you tap on that notification that will help you to navigate to a specific screen.
so navigate without context like this.
navigatorKey.currentState
?.pushNamed(AnotherScreen.routeName);
if you want to pass arguments
navigatorKey.currentState
?.pushNamed(IOSNotificationIntentScreen.routeName, arguments: {
"arg1": "hello arg 1",
"arg2": "hello arg 2",
});
Related
I am developing an app in flutter. I used awesome_notification package to show notifications. In awesome_notification, onActionReceivedMethod works fine when an app is in the foreground but does not work when an app is in the background. How to handle this?
#pragma("vm:entry-point")
static Future <void> onActionReceivedMethod(ReceivedAction receivedAction) async {
// code to navigate some page
} // not calling when user tap notification
Also, onMessageOpenedApp function is not triggered when tapping the notification. How to solve this? Kindly help me to resolve this.
FirebaseMessaging.onMessageOpenedApp.listen((message) async {
// code to navigate some page
}); // not calling when user tap notification
I encountered the same problem with local_notification, to pass an information from the vm entry point to the app.
I solved it by doing an isolate sent / receive like this:
//needed for isolate
import 'dart:isolate';
import 'dart:ui';
//outside main
const String channel = 'channel_key';
#pragma('vm:entry-point')
void onReceiveBackgroundResponse(NotificationResponse notificationResponse) async {
final sendPort = IsolateNameServer.lookupPortByName(channel);
sendPort?.send(notificationResponse.actionId);
}
...
//inside main
void listenNotification() {
final receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort, channel);
receivePort.asBroadcastStream().listen((event) {
print(event);
});
}
in my app firebase push notification is implemented.
on click event works when app is terminated.
but how to achieve notification on click of redirection when app is open.
class _Application extends State<Application> {
// It is assumed that all messages contain a data field with the key 'type'
Future<void> setupInteractedMessage() async {
// Get any messages which caused the application to open from
// a terminated state.
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
// If the message also contains a data property with a "type" of "chat",
// navigate to a chat screen
if (initialMessage != null) {
_handleMessage(initialMessage);
}
// Also handle any interaction when the app is in the background via a
// Stream listener
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
}
void _handleMessage(RemoteMessage message) {
if (message.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(message),
);
}
}
#override
void initState() {
super.initState();
// Run code required to handle interacted messages in an async function
// as initState() must not be async
setupInteractedMessage();
}
I am using the plugin flutter_local_notifications to show a user notifications on the app. I have managed to show the notifications on the app and the user can click on the notifications, the user will be redirected to see all pending items from the notification in a notification page. I have set the function to detect the onclick on initstate. The whole function and method to show notification I have implemented it on the homescreen.
#override
void initState() {
super.initState();
initPlatformState();
getNotifications();
NotificationApi.init(initScheduled: true);
init();
_configureSelectNotificationSubject(); //function to redirect to Notifications page
}
which goes to
void _configureSelectNotificationSubject() {
print("clicked is the notification");
NotificationApi.onNotifications.stream.listen((String? payload) async {
await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => WANotifications(current: current)),
(Route<dynamic> route) =>
true);
});
}
The challenge am facing with this implementation is that when a user clicks to go to the home screen , the user gets redirected automatically to the notifications page from the home screen without his/her consent. The redirect should only occur when they click on the notifications.
How can I set the redirect to only occur when the user clicks the notification only and not when they click to go to home screen
You can achieve this with the help of didNotificationLaunchApp.
bool get didNotificationLaunchApp =>
notificationAppLaunchDetails?.didNotificationLaunchApp ?? false;
// Use here,
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
Pl check full example here : https://pub.dev/packages/flutter_local_notifications/example
For non main file :
#override
void initState() {
super.initState();
localNotification(); // call below localNotification() here.
}
localNotification() async {
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin!.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
// redirect to new screen if true.
}
}
When I receive a notification when my app is killed, I want to navigate to a specific screen of my choice when I tap on the notification. I have added the code for the navigation but the app does not navigate to that screen. Instead, the app opens and navigates to the first screen.
Here's my code:
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
RemoteNotification notification = message.notification;
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channel.description,
icon: '#drawable/splash',
playSound: true
),
));
// This does not seem to work as I am not directed to OrdersScreen
navigatorKey.currentState
.push(MaterialPageRoute(builder: (_) => OrdersScreen()));
}
void initState(){
super.initState();
var initializationSettingsAndroid = AndroidInitializationSettings('#drawable/splash');
var initializationSettings = InitializationSettings(android:initializationSettingsAndroid);
flutterLocalNotificationsPlugin.initialize(initializationSettings);
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
if (message != null) {
navigatorKey.currentState
.push(MaterialPageRoute(builder: (_) => OrdersScreen()));
}
});
}
If you are using firebase_messaging version 10.0.0 or above, the below code should work.
You should call handlePushNotification() function in your first screen.
To save last foreground screen path, you can use shared_preferences or another solution.
This is a very basic example for your problem.
void handlePushNotification() {
//FOREGROUND MESSAGES
FirebaseMessaging.onMessage.listen((message) {
_showSnackBar(message: message);
});
//BACKGROUND MESSAGES - ONRESUME
FirebaseMessaging.onMessageOpenedApp.listen((message) {
//Detect your current screen if you wish when "onResume" called.
if( currentScreenPath == 'FirstScreen' ){
_navigateSpecificScreen();
} else {
debugPrint('Specific screen is already foreground!');
_showSnackBar(message: message);
}
});
//BACKGROUND MESSAGES - ONLAUNCH
if (_firebaseMessaging != null) {
_firebaseMessaging!.getInitialMessage().then((message) {
if (null != message) {
_navigateSpecificScreen();
}
});
}
}
If your only problem is navigate to a specific screen when your app killed and relaunch, you can ignore the following.
If your application is more extensive, you should prefer to control all your navigations from a single class.
Maybe you can use onGenerateRoute property in MaterialApp.
This is an example: how to use onGenerateRoute
You can create a function for track the which screen is foreground in your RouteGenerator class.
You also need to handle Navigation.of(context).pop().
You don't want to navigate it again when a screen is already foreground when onResume called.
for me, I am using awesome notification to do that. this is done inside the home screen.
I do not know if this is the best way, but it works for me perfectly.
You also can distinguish the buttons inside the notification(if there are) using receivedNotification.buttonKeyPressed.
if the user does not press the button (or there is no button), so there is no buttonkeypressed, and I handled this in my code.
awsome notifications link: https://pub.dev/packages/awesome_notifications
////////////////////////////////////////////////////////////////////////////
///////////////////////////////// HOME SCREEN //////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
try {
AwesomeNotifications().actionStream.listen((receivedNotification) {
// print(receivedNotification.toString());
// print(receivedNotification.buttonKeyPressed.toString());
print("I am listning");
////////////// REPLY button
if (receivedNotification.buttonKeyPressed == "REPLY") {
Navigator.of(context).pushNamed(
ReplayInterface.routeName,
);
}
///////////////// REJECT button
else if (receivedNotification.buttonKeyPressed == "REJECT") {
getReject();
Navigator.of(context).pushNamed(RegectInterface.routeName);
}
///////////////// BODY Notification
else if (receivedNotification.buttonKeyPressed != "REJECT" &&
receivedNotification.buttonKeyPressed != "REPLY") {
print(receivedNotification.buttonKeyPressed);
Navigator.of(context).pushNamed(Counter.routName);
}
///////////////// NOT NOTIFICATION
else {
return;
}
});
} catch (e) {
print("e is $e");
Navigator.of(context).pushReplacementNamed("/");
}
super.initState();
}
`
I'm using StateNotifier and Riverpod.
I have a notification page, which contains a list of notification. When a notification arrives, I trigger a refresh for notification. However, when I navigate to notification page, it still using the old (cached?) list.
How do I refresh a page offscreen?
Foreground Message
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_localNotification.showLocalNotification(message);
ProviderContainer().read(notificationProvider).getNotification();
});
Background Message
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
final LocalNotification localNotification = LocalNotification();
await localNotification.init();
localNotification.showLocalNotification(message);
ProviderContainer().read(notificationProvider).getNotification();
}
NotificationProvider
Future<void> getNotification() async {
try {
state = const NotificationLoadingState();
_notificationList = await _notificationRepository.getNotification();
state = NotificationLoadedState(_notificationList); // ==> I get a new list here
} catch (e, s) {
state = const NotificationErrorState('error fetching notification');
}
}
UI
final state = watch(notificationProvider.state);
if (state is NotificationLoadingState) {
return _buildLoading();
} else if (state is NotificationLoadedState) {
return _buildLoaded(state.notificationList); // ==> I still get the old list here
} else if (state is NotificationErrorState) {
return _buildError(state.message);
}
Edit:
I managed to solve the foreground message handler by using navigatorKey.currentContext.
However, I still haven't solved background message handler.
I've tried changing my main.dart to use UncontrolledProviderScope with a global ProviderContainer, which then was called from background message handler. It's still not refreshing.
With this line you are creating a new instance for your providers :
ProviderContainer().read(notificationProvider).getNotification();
you need to use a context to get the existing instance of ProviderContainer:
context.read(notificationProvider).getNotification();
or if you are not inside ui make dependencies between your providers