Firebase Push Notification in Flutter when App Kiled - flutter

I'm having an issue dealing with Firebase push notifications when the App is KILLED.
What's happening:
First of all, push notifications should work like this. When you tap, you are redirected to a Move-Type Job or an Event-Type Job.
When app is in BACKGROUND MODE, push notification shows as it should,
and it redirects to the page that it should.
When App is KILLED, push notifications still shows, but when you tap on them you are not redirected, you are just opening the App.
Future initialize() async {
await getConnectivity();
if (hasInternet) {
try {
await configurePushNotificationHandlers();
} catch (e) {
hasApiConnection = false;
}
}
}
Future configurePushNotificationHandlers() async {
await navigationService.navigateReplacementWithParams(ErrorPage());
await _firebaseMessaging.requestPermission(
alert: true,
badge: true,
provisional: false,
sound: true,
);
String fbToken = await _firebaseMessaging.getToken();
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (message.data != null && message.data.containsKey('showInPageNotification')) {
numberOfNotifications++;
}
notifyListeners();
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
print('A new onMessageOpenedApp event was published!');
if (message.data != null && message.data.containsKey('job_id')) {
String jobId = message.data['job_id'];
String jobType = message.data['job_type'];
jobType = jobType.toLowerCase();
if (jobType == 'move') {
await goToJobDetail(jobId);
} else {
await goToEventDetail(jobId);
}
}
});
Anyone has a clue of why does this happens? Push notifications are working fine, it's the redirection the current ISSUE. Thanks!

Use FirebaseMessaging.instance.getInitialMessage() method to get messages
If App is Closed/Killed
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
print("FirebaseMessaging.getInitialMessage $message");
});
Try this and let me know.

Related

Can't Read Data of Notification Message - Flutter

I used postman for Creating a Notification.Also I used Firebase Cloud Messaging as backend.
All works, even Notification pops in the android. But I can't read the data in my debug console ,
the Following errors are shown in console:
D/FLTFireMsgReceiver(23051): broadcast received for message
W/FLTFireMsgService(23051): A background message could not be handled in Dart as no onBackgroundMessage handler has been registered.
W/FirebaseMessaging(23051): Missing Default Notification Channel metadata in AndroidManifest. Default value will be used.
The Codes of the Push Notification class:
import 'dart:io';
import 'package:cholachol_drive/globalvariables.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
class PushNotificationService {
// final FirebaseMessaging fcm = FirebaseMessaging.instance;
Future initialize(context) async {
FirebaseMessaging.onMessage.listen((event) {
(Map<String, dynamic> message) async {
// retrieveRideRequestInfo(getRideRequestId(message), context);
print("onMessage: $message");
if (Platform.isAndroid) {
String rideID = message["data"]['ride_id'];
if (rideID != null) {
print("Fucked");
} else {
print('ride_id: $rideID');
}
}
};
});
FirebaseMessaging.onMessageOpenedApp.listen((event) {
(Map<String, dynamic> message) async {
// retrieveRideRequestInfo(getRideRequestId(message), context);
print("onLaunch: $message");
if (Platform.isAndroid) {
String rideID = message["data"]['ride_id'];
if (rideID != null) {
print("Fucked");
} else {
print('ride_id: $rideID');
}
}
};
});
}
Future<String> getToken() async {
String token = await FirebaseMessaging.instance.getToken();
print("token: $token");
DatabaseReference tokerRef = FirebaseDatabase.instance
.ref()
.child("drivers/${currentFirebaseUser.uid}/token");
FirebaseMessaging.instance.subscribeToTopic('alldrivers');
FirebaseMessaging.instance.subscribeToTopic('allusers');
}
}
It seems you don't have onBackgroundMessage implemented in your app. You need to use onBackgroundMessage in order to handle background messages. onMessage will only handle foreground messages.

How can I run background code when a notification is executed with Flutter?

I am using flutter_local_notification and workmanager plugins in order to run some background code when a notification is generated (only Android). This is how flutter_local_notification is initialised:
final StreamController<ReceivedNotification> didReceiveLocalNotificationSubject = StreamController<ReceivedNotification>.broadcast();
Future<void> init() async {
await _configureLocalTimeZone();
notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails!.didNotificationLaunchApp) {
selectedNotificationPayload = notificationAppLaunchDetails!.notificationResponse?.payload;
}
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
InitializationSettings initializationSettings = const InitializationSettings(
android: initializationSettingsAndroid,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse:
(NotificationResponse notificationResponse) {
switch (notificationResponse.notificationResponseType) {
case NotificationResponseType.selectedNotification:
case NotificationResponseType.selectedNotificationAction:
// if (notificationResponse.actionId == navigationActionId) {
selectNotificationSubject.add(notificationResponse.payload);
selectedNotificationPayload = notificationResponse.payload;
// }
didReceiveLocalNotificationSubject.add(
ReceivedNotification(
id: notificationResponse.id!,
title: notificationResponse.actionId,
body: 'stuff',
payload: notificationResponse.payload,
),
);
break;
}
},
// onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
);
_notificationsEnabled = await _isAndroidPermissionGranted();
_notificationsEnabled = await _requestPermissions();
_configureDidReceiveLocalNotificationSubject();
}
and this is the code that gets executed with Workmanager:
void _configureDidReceiveLocalNotificationSubject() {
didReceiveLocalNotificationSubject.stream
.listen((ReceivedNotification receivedNotification) async {
var title = receivedNotification.title ?? 'UNKNOWN';
Workmanager().registerOneOffTask(
"my.simpleTask",
"my.simpleTask",
inputData: <String, dynamic>{
'string': title,
},
);
});
}
Currently I have two problems with that code:
the Workmanager's task is run only when the user tap the notification
the Workmanager's task won't be executed if the app is terminated by the user first, even if the notification is generated (and tapped)
How can I make the Workmanager's task to be executed as soon as the notification is generated (without the user tapping) with the application terminated or not?
i assume you are scheduled local notification and execute some function in there.
TL:DR
first: scheduled local notification not able to execute function in background
eg:
int randomInt = Random().nextInt();
await flutterLocalNotificationsPlugin.zonedSchedule(
0,
'scheduled notif with int $randomInt',
....
when you are registed this notif, you will get randomInt, and then on the notification it will show the int that you get first time its scheduled. which means, its only show notification,Random().nextInt is not executed.
secondly,
void _configureDidReceiveLocalNotificationSubject() {
didReceiveLocalNotificationSubject.stream
stream function will be terminated too after the apps is killed. except you are bring it into foreground. the stream will keep listening any changes.
How can I make the Workmanager's task to be executed as soon as the
notification is generated
I think you are missed on this part. the correct way is:
Register Workmanager and then inside the callback function, you can generated local notification.
register your WM in your initState
Workmanager().registerOneOffTask(
"task-identifier",
simpleTaskKey,
initialDelay: Duration(minutes: 30), // you can use this delay for scheduling
);
then in the callback funtion generete local notificaiton
#pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
// you function execute here
// eg: final tempInt = Random.nextInt();
// then we can use the tempInt
show local notification function here
return Future.value(true);
});
}

flutter - don't show notification when user is on certain page

My app has chatting function. And I have implemented Firebase Cloud Messaging and Local Notification.
So when someone sends me a message, notification(one inside android status bar) and local notification(one on the upper part of the screen that holds for like 3 seconds) is shown. They show up when app is opened, on background, and also when app is terminated. (So far so good)
But I don't want both notifications(1. fcm notification, 2. local notification) to show up when I'm already in the chat page with that person. And I have no idea where to start.
Below is my code for handling fcm and show local notification. They are inside main.dart and the main function calls initializeFCM before returning my root page.
Future<void> initializeFCM() async {
await Firebase.initializeApp();
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Firebase Messaging
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// print('User granted permission');
} else
if (settings.authorizationStatus == AuthorizationStatus.provisional) {
// print('User granted provisional permission');
} else {
// print('User declined or has not accepted permission');
}
// Get any messages which caused the application to open from
// a terminated state.
RemoteMessage? initialMessage = await messaging.getInitialMessage();
if (initialMessage != null) {
handleMessage(initialMessage);
}
// initialize local notification
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// terminated app을 local notification 눌러서 열었을 때 - payload가 정상적으로 동작하지 않음
final NotificationAppLaunchDetails? notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
final didNotificationLaunchApp = notificationAppLaunchDetails?.didNotificationLaunchApp ?? false;
if (didNotificationLaunchApp) {
onSelectNotification(notificationAppLaunchDetails!.payload);
}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
showLocalNotification(message);
});
FirebaseMessaging.onBackgroundMessage(showLocalNotification);
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
}
void handleMessage(RemoteMessage message) {
if (message.data["screen"] == "message") {
Navigator.push(
navigatorKey.currentContext!,
MaterialPageRoute(
builder: (context) => MessageListPage(),
)
);
}
}
Future<void> onSelectNotification(payload) async {
if(payload != null) {
Map<String, dynamic> data = json.decode(payload);
if (data['screen'] == "message") {
Navigator.push(
navigatorKey.currentContext!,
MaterialPageRoute(
builder: (context) => MessageListPage(),
)
);
}
}
}
Future<void> showLocalNotification(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
// String screen = message.data['screen'];
// iOS heads up notification setting
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
// android channel setting
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
importance: Importance.max,
);
// initialize local notification
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin.initialize(InitializationSettings(
android: AndroidInitializationSettings('#drawable/mentea_ic_stat_name'),
iOS: IOSInitializationSettings()),
onSelectNotification: onSelectNotification);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
// If `onMessage` is triggered with a notification, construct our own
// local notification to show to users using the created channel.
if (notification != null && android != null) {
// print('Message also contained a notification: ${message.notification}');
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
'high_importance_channel', //channel.id,
'High Importance Notifications', //channel.name,
icon: android.smallIcon,
color: primaryColor,
// other properties...
),
),
payload: json.encode(message.data)
);
}
}
I don't know if you still have this problem, but I'll post my solution here.
It happens that when we send a push notification with the notification message feature, the FCM plugin automatically sends a notification on the device if the App is in background and finished, where we can only customize the foreground notification through Local Notifications or other solutions within of the App. So the solution pointed out in the Firebase documentation for this situation is to send the push notification only as a data message, without including the notification object. Below I show the examples and the documentation link.
Documentation: https://firebase.google.com/docs/cloud-messaging/concept-options?authuser=1&hl=pt#notifications_and_data_messages
Combined notification example:
const payload = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
data: {
stock: 'GOOG',
open: '829.62',
close: '635.67'
}
};
Notification example with data message only:
const payload = {
data: {
score: '850',
time: '2:45'
}
};
Notification example with notification message only:
const payload = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
}
};
So, so that you can customize all forms of notification (foreground, background and terminated), send a push notification through your backend as a data message only.

Send push notifications in different languages using FCM

I've added FCM in my Flutter app and it works, but I can't find a way to send notifications in different languages.
I saw that you can set the conditions for the language on the Firebase console when sending a new notification, but I'm not quite sure how it works. I would like to know if I need to add something to my code to make it work.
Here's the code I have:
push_notification_service.dart
import 'package:firebase_messaging/firebase_messaging.dart';
enum AppState {
foreground,
background,
terminated,
}
class PushNotificationsManager {
PushNotificationsManager._();
factory PushNotificationsManager() => _instance;
static final PushNotificationsManager _instance = PushNotificationsManager._();
Future<void> init() async {
await _setFCMToken();
_configure();
}
_setFCMToken() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
String? token = await messaging.getToken();
print('FirebaseMessaging token: $token');
}
}
void _configure() async {
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_showForegroundNotificationInAndroid(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_handleNotification(message: message.data, appState: AppState.foreground);
});
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
_handleNotification(message: initialMessage.data, appState: AppState.terminated);
}
}
void _showForegroundNotificationInAndroid(RemoteMessage message) async {}
void _handleNotification({
Map<String, dynamic>? message,
AppState? appState,
}) async {
print('PushNotificationsManager: _handleNotification ${message.toString()} ${appState.toString()}');
}
}
and in main.dart I have:
initState(){
super.initState();
PushNotificationsManager().init();
}
Do I need to use flutter_analytics package?

Flutter incoming video/audio call notification using Agora

I have been working on an application and I need to implement in app audio and video calling in my app which I have done using Agora.io but the issue is I have to display incoming call notification does not matter if app is in foreground or in background. I have tried many things but still I am unable to configure that out. I am using agora_rtc_engine package for making calls.
Any help would be appreciated.
Thanks
The code I am working with currently:
Call Methods
class CallMethods {
final callRef = FirebaseFirestore.instance.collection('Calls');
Stream<DocumentSnapshot> callstream({#required String id}) =>
callRef.doc(id).snapshots();
Future<bool> makeCall({#required Call call}) async {
try {
log('Making call');
call.hasdialed = true;
Map<String, dynamic> hasDialedMap = call.toMap(call);
call.hasdialed = false;
Map<String, dynamic> hasNotDialedMap = call.toMap(call);
await callRef.doc(call.senderid).set(hasDialedMap);
await callRef.doc(call.receiverid).set(hasNotDialedMap);
return true;
} catch (e) {
print(e);
return false;
}
}
Future<bool> endCall({#required Call call}) async {
try {
log('ending call');
await callRef.doc(call.senderid).delete();
await callRef.doc(call.receiverid).delete();
return true;
} catch (e) {
print(e);
return false;
}
}
}
Call Utils: Which is used to make calls
class CallUtils {
static final CallMethods callmethods = CallMethods();
static dial(
BuildContext context, {
#required User from,
#required var to,
}) async {
Call call = Call(
senderid: from.id,
// senderpic: from.avatar.url,
callername: from.name,
receiverid: to.id,
// receiverpic: to.avatar.url,
receivername: to.name,
channelid: Random().nextInt(999999).toString(),
);
bool callmade = await callmethods.makeCall(call: call);
call.hasdialed = true;
if (callmade) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoCallScreen(call: call),
),
);
}
}
}
After that I have a pickup layout which is used to wrap all the screens to display incoming call notification.
Pickup Call Layout:
(user.value.id != null)
? StreamBuilder<DocumentSnapshot>(
stream: callmethods.callstream(id: user.value.id),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.data() != null) {
Call call = Call.fromMap(snapshot.data.data());
if (!call.hasdialed) {
return PickupScreen(call: call);
} else {
return widget.scaffold;
}
} else {
return widget.scaffold;
}
},
)
: widget.scaffold,
It can be done via firebase push notifications & backend API service.
Sender side:
As soon as a call is made, you would post your backend api service with caller and receiver id, and your backend service is further responsible to send a push notification with a payload to the receiver.
Receiver side:
When receiver gets a push notification, you can configure it to open your app automatically and show a screen with all the payload information. Maybe you can show him a screen with accept and decline button and if he accepts, you can connect him to Agora.
Check this for payload configuration.