I have implemented fcm and added flutter local notification with action button for getting incomming call notification and it works on debug mode perfectly but it is not working in release mode. I have added all necessary permission. And I know most of os will kill background task but I need to know how to tackle those issues.Somebody please help me and I experianced similar issue in a live tracking app some os have killed the task after some time and navigation stopped
when a push notification comes Ineed to get call notification with ringtone with action button either with flutter local notification or flutter call kit.
Also Im doing a live tracking app I need to track continuosly. In that app I used geolocator , flutter background service etc the issue is navigation is getting stopped after some time in some phones
import 'dart:convert';
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hope/core/services/local_storage.dart';
import 'package:hope/core/utils/colors.dart';
import 'package:hope/core/view_model.dart/widgetprovider/bottomnav_provider.dart';
import 'package:hope/views/screens/matches/call_view.dart';
import 'package:hope/views/screens/matches/incomming_call.dart';
import 'package:hope/views/screens/notification/notification_view.dart';
import 'package:just_audio/just_audio.dart';
@pragma('vm:entry-point')
void onSelectNotification(NotificationResponse notificationResponse) async {
final payload = notificationResponse.payload;
if (payload != null && payload.isNotEmpty) {
Map<String, dynamic> data = Map<String, dynamic>.from(jsonDecode(payload));
log("Notification payload: ${data.toString()}");
String messageType = data['type'];
if (notificationResponse.actionId == 'accept_call') {
// Stop the audio first
await audioPlayer.stop();
audioPlayer.dispose();
// Cancel the notification
if (notificationId != null) {
flutterLocalNotificationsPlugin.cancel(notificationId!);
}
// Navigate to the call view
navigatorKey.currentState?.push(
MaterialPageRoute(
builder: (_) => CallView(
channelId: data['channelId'],
agoraToken: data['agora_token'],
contactName: data['caller_name'],
),
),
);
return; // Early return to prevent further execution
} else if (notificationResponse.actionId == 'reject_call') {
// Stop the audio first
await audioPlayer.stop();
audioPlayer.dispose();
// Cancel the notification
if (notificationId != null) {
flutterLocalNotificationsPlugin.cancel(notificationId!);
}
// Since this is a reject action, do not navigate to any page
log('Call Rejected');
return; // Early return to prevent further navigation
}
// Handle other message types based on 'messageType'
if (messageType == 'missed_call') {
BottomNavProvider().setPage(3);
} else if (messageType == 'chat') {
navigatorKey.currentState?.push(
MaterialPageRoute(
builder: (_) => IncomingCallView(
channelId: data['channelId'],
agoraToken: data['agora_token'],
contactName: data['channelId'],
),
),
);
} else if (messageType == 'new_interest') {
navigatorKey.currentState?.push(
MaterialPageRoute(
builder: (_) => IncomingCallView(
channelId: data['channelId'],
agoraToken: data['agora_token'],
contactName: data['caller_name'],
),
),
);
} else if (messageType == 'notification') {
navigatorKey.currentState?.push(
MaterialPageRoute(
builder: (_) =>
NotificationView(), // Navigate to the notification screen
),
);
}
} else {
log("Notification payload is null or empty.");
}
}
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
AudioPlayer audioPlayer = AudioPlayer();
int? notificationId;
class PushNoti {
String? _fcmToken;
final FirebaseMessaging _fcm = FirebaseMessaging.instance;
Future<void> requestNotificationPermission() async {
await _fcm.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
_fcmToken = await _fcm.getToken();
log('device FCM token is $_fcmToken');
LocalStorage.setString({'fcmToken': _fcmToken.toString()});
_fcmToken = await _fcm.getToken();
}
Future<void> initPushNotification() async {
_fcmToken = await _fcm.getToken();
log('device FCM token is $_fcmToken');
LocalStorage.setString({'fcmToken': _fcmToken.toString()});
// Handle incoming messages when the app is in the foreground
FirebaseMessaging.onMessage.listen((RemoteMessage? message) async {
// showNotification(message!);
// If it's a call type message, show the incoming call screen
if (message!.data.isNotEmpty && message!.data['type'] == 'call') {
audioPlayer.play();
navigatorKey.currentState
?.push(
MaterialPageRoute(
builder: (_) => IncomingCallView(
channelId: message!.data['channelId'],
agoraToken: message.data['agora_token'],
contactName: message.data['caller_name'],
),
),
)
.then((_) {
if (notificationId != null) {
audioPlayer.stop();
flutterLocalNotificationsPlugin.cancel(notificationId!);
}
});
} else {
showNotification(message);
}
});
// Handle notification taps when the app is in the background or terminated
FirebaseMessaging.onMessageOpenedApp
.listen((RemoteMessage? message) async {});
// Check if the app was opened from a terminated state due to a notification tap
_fcm.getInitialMessage().then((RemoteMessage? message) {});
}
}
void showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (message.data['type'] == 'call') {
await audioPlayer.setAsset(
'assets/ringtone/ringtone.mp3'); // Load the ringtone from assets
audioPlayer.play();
log('hello world tttt ${message.data.toString()}');
}
var platformChannelSpecifics = NotificationDetails(
android: message.data['type'] == 'call'
? AndroidNotificationDetails(
'call_channel', // For call notifications
'Incoming Call',
importance: Importance.max,
priority: Priority.max,
// sound: RawResourceAndroidNotificationSound('isa_63861'), // Custom sound for calls (optional)
playSound: true,
color: UiColor.primary,
actions: <AndroidNotificationAction>[
AndroidNotificationAction(
'accept_call',
'Accept',
// icon: DrawableResourceAndroidBitmap("ic_accept"),
showsUserInterface: true,
),
AndroidNotificationAction(
'reject_call',
'Reject',
// icon: DrawableResourceAndroidBitmap("ic_reject"),
showsUserInterface: true,
),
],
)
: AndroidNotificationDetails(
'important_notifications', // For other notifications
'Important Notifications',
importance: Importance.high,
color: UiColor.primary,
// sound: RawResourceAndroidNotificationSound("notification_sound"), // Optional custom sound
actions: [], // No actions for other notifications (can be added dynamically)
),
iOS: DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
),
);
if (notification != null && android != null) {
notificationId = notification.hashCode;
flutterLocalNotificationsPlugin.show(notification.hashCode,
notification.title, notification.body, platformChannelSpecifics,
payload: jsonEncode(message.data));
}
}
Future<void> initLocalNotifications() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('mipmap/ic_launcher');
const InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: DarwinInitializationSettings(),
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onDidReceiveBackgroundNotificationResponse:
(NotificationResponse notificationResponse) async {
// Handle background notification response here
log('Background notification received from local notoifction: ${notificationResponse.payload}');
}, onDidReceiveNotificationResponse: onSelectNotification);
}
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
// Show the notification first
showNotification(message);
// Check if the message type is 'call'
if (message.data['type'] == 'call') {
// Wait for 25 seconds
await Future.delayed(Duration(seconds: 25));
// Remove the notification after 25 seconds
flutterLocalNotificationsPlugin.cancel(message.notification.hashCode);
log('Notification removed after 25 seconds for call type.');
}
}
Future<void> firebaseMessagingForegroundHandler(RemoteMessage message) async {
if (message.data['type'] == 'call') {
await audioPlayer.setAsset(
'assets/ringtone/ringtone.mp3'); // Load the ringtone from assets
audioPlayer.play();
} else {
showNotification(message);
}
}
2
Answers
Did you add these lines of code?
You need to configure a top level or static method which will handle the action:
Specify this function as a parameter in the initialize method of this plugin:
For me its resolved by unrestricting battery optimsation in app battery setting which can be find in devices setting-> apps -> [‘app name’]->battery and turn off toggole for (cancel permission when app in unused) . After changing this setup its start working