I have developed my application in flutter. iOS worked fine but Android application is displaying notification twice when my application is in terminated state.
I have review that, only Google Play Store application displaying notification twice, I am not able to reproduce the scenario in my debug or release build locally.
Any help/answer is appreciated.
Here is my flutter code
main.dart
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Future.delayed(const Duration(milliseconds: 300));
await Firebase.initializeApp();
FlutterError.onError = (details) {
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
print("notification main.dart called");
// FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
await PushNotificationService().initNotifications();
await Get.putAsync(() => StorageService().init());
Get.put(ScrollControllerHelper());
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]).then((_) {
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
final CheckAuthController userController = Get.put(CheckAuthController());
MyApp({super.key});
@override
Widget build(BuildContext context) {
ScreenUtil.init(context);
return GetMaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
title: Strings.appTitle,
initialRoute: userController.isOnBoardingDisplayed == true ? RouteConstants.dashboard : RouteConstants.onboarding,
initialBinding: userController.isOnBoardingDisplayed == true ? DashboardBinding() : OnboardingBindings(),
getPages: Pages.getPages,
theme: ThemeData(
scaffoldBackgroundColor: ColorConstants.appBackgroundColor,
),
defaultTransition: Transition.rightToLeft,
translations: AppTranslation(),
locale: const Locale(Constants.languageGujarati),
fallbackLocale: const Locale(Constants.languageGujarati),
supportedLocales: const [
Locale(Constants.languageEnglish),
Locale(Constants.languageGujarati),
Locale(Constants.languageHindi),
],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
);
}
}
push_notification_service.dart
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:prime_debut_flutter/routes/routes.dart';
import 'package:prime_debut_flutter/views/dashboard/dashboard_controller.dart';
class PushNotificationService {
final Set<String> _handledMessageIds = {};
PushNotificationService._internal();
static final PushNotificationService _instance =
PushNotificationService._internal();
factory PushNotificationService() => _instance;
final _firebaseMessaging = FirebaseMessaging.instance;
bool _isInitialized = false;
final androidChannel = const AndroidNotificationChannel(
'android_channel', 'android_notification_channel',
description: 'notification', importance: Importance.high);
final _localNotifications = FlutterLocalNotificationsPlugin();
void handleMessage(RemoteMessage? message) async {
//when app is minimized and notification come and tap
print("notification in push service detail = $message");
var messageData = message?.data;
print("notification in push service detail = $messageData");
Map? notificationPayload = messageData;
print("notification in push service detail = $notificationPayload");
Map? dataPayload = notificationPayload;
print("notification in push service detail = $dataPayload");
String? postId = dataPayload?['id'];
String? notificationId = dataPayload?['notification_id'];
print("notification in push service postId = $postId");
print("notification in push service notificationId = $notificationId");
if (message == null) return;
var controller = Get.find<DashboardController>();
controller.postId = postId ?? "";
controller.notificationId = notificationId;
controller.getHomePost(isFromNotification: true,isFromReload: false,isForceRefresh: false);
controller.apiReadNotification();
}
void handleMessageMinimize(RemoteMessage? message) async {
//when app is minimized and notification come and tap
print("notification in push service detail = $message");
var messageData = message?.data;
print("notification in push service detail = $messageData");
Map? notificationPayload = messageData;
print("notification in push service detail = $notificationPayload");
Map? dataPayload = notificationPayload;
print("notification in push service detail = $dataPayload");
String? postId = dataPayload?['id'];
String? notificationId = dataPayload?['notification_id'];
print("notification in push service postId = $postId");
if (message == null) return;
var controller = Get.find<DashboardController>();
controller.postId = postId ?? "";
controller.notificationId = notificationId;
controller.getHomePost(isFromNotification: true, isFromReload: false,isForceRefresh: false);
controller.apiReadNotification();
Routes.goToDashboardView(isFromNotification: true, postId: postId,notificationId: notificationId);
}
Future initLocalNotifications() async {
const android = AndroidInitializationSettings('app_logo');
const ios = DarwinInitializationSettings();
const settings = InitializationSettings(android: android, iOS: ios);
await _localNotifications.initialize(
settings,
onDidReceiveNotificationResponse: _onReceived,
onDidReceiveBackgroundNotificationResponse: _onReceived,
);
final platform = _localNotifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(androidChannel);
_localNotifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()?.requestNotificationsPermission();
}
Future initPushNotifications() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
FirebaseMessaging.instance.getInitialMessage().then((message){
print("notification in push service getInitialMessage = $message");
handleMessageMinimize(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((message){
print("notification in push service onMessageOpenedApp = $message");
//when app is minimized and notification come and tap
handleMessageMinimize(message);
});
FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
print("notification in FirebaseMessaging.onMessage");
FirebaseMessaging.onMessage.listen((message) {
final data = message.data;
final dedupeKey = (data['notification_id'] ??
message.messageId ??
'${data['id']}_${message.sentTime?.millisecondsSinceEpoch ?? ''}')
.toString();
if (dedupeKey.isNotEmpty && _handledMessageIds.contains(dedupeKey)) return;
if (dedupeKey.isNotEmpty) _handledMessageIds.add(dedupeKey);
final nidStr = (data['notification_id'] ?? '').toString();
final int notifId = int.tryParse(nidStr)
?? (nidStr.isNotEmpty
? nidStr.hashCode
: DateTime.now().millisecondsSinceEpoch.remainder(100000));
print("========== FCM ==========notifId = $notifId");
print("messageId: ${message.messageId}");
print("from: ${message.from}");
print("sentTime: ${message.sentTime}");
print("notification in local notification ${message.notification?.title}");
final notification = message.notification;
if (notification != null) {
_localNotifications.show(
notifId,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
androidChannel.id, androidChannel.name,
channelDescription: androidChannel.description,
icon: 'app_logo',showWhen: false,)),
payload: jsonEncode(message.toMap()));
}
});
}
Future<void> initNotifications() async {
print("notification initNotifications called");
if (_isInitialized) {
print("PushNotificationService already initialized");
return;
}
_isInitialized = true;
await _firebaseMessaging.requestPermission();
String? apnsToken;
if (Platform.isMacOS || Platform.isIOS) {
apnsToken = await _firebaseMessaging.getAPNSToken();
}
if ((Platform.isIOS && apnsToken != null) || Platform.isAndroid) {
generateFcmToken();
_firebaseMessaging.subscribeToTopic('local_news_set_up');
print("topic subscribed");
}
await initLocalNotifications();
await initPushNotifications();
}
generateFcmToken() async{
final fcmToken = await _firebaseMessaging.getToken();
print("Fcm token --- ${fcmToken}");
}
void unSubscribe(){
_firebaseMessaging.unsubscribeFromTopic('local_news_set_up');
}
}
void _onReceived(NotificationResponse details) async {
//when app is opened at that time navigation occurred
print("notification 1 detail = $details");
var messageData = details.payload;
Map notificationPayload = (jsonDecode(messageData!));
Map dataPayload = (notificationPayload["data"]);
var postId = dataPayload['id'];
String? notificationId = dataPayload['notification_id'];
print("Raw payload: $postId and notificationId = $notificationId");
var controller = Get.find<DashboardController>();
controller.postId = postId;
controller.notificationId = notificationId;
controller.homePostListPage = 1;
controller.getHomePost(isFromNotification: true, isFromReload: false,isForceRefresh: false);
controller.apiReadNotification();
}
Future<void> handleBackgroundMessage(RemoteMessage message) async {
print("notification 2 detail = $message");
print("message back-- ${message.notification}");
}