androidflutterfirebasepush-notificationfirebase-cloud-messaging

Flutter FCM: Custom Sound Plays Alongside Default Sound & Redirection Fails When App is Terminated


  1. I'm using Firebase Cloud Messaging (FCM) in my Flutter app and trying to achieve the following:

  2. Play a custom notification sound when a notification is received. Redirect to a specific screen when the notification is clicked, even if the app is terminated or not running in the background.

Issues:

  1. Custom Sound Problem:

    • When I include the notification object in the payload, the notification plays the default sound instead of the custom sound.
    • If I include both notification and custom sound configuration in the android section, I receive two notifications:
      • One with the default sound.
      • Another with the custom sound.
  2. Redirection Problem:

    • When I remove the notification object and rely entirely on the data object, the custom sound works, but redirection fails if the app is terminated or not in the background.
    • The app doesn't receive the notification click event in such cases.

Payload I'm Using:

Below is the payload I'm sending to FCM:
    {  
     "message": {
       "token": "DEVICE_FCM_TOKEN",  
       "notification": {  
         "title": "Visitor has been admitted!",  
         "body": "Dhaval developer (Visitor) has been admitted.",
       },  
      "android": {  
        "notification": {  
          "sound": "visitor_notification_sound"  
        }  
      },
      "apns":{
        "payload":{
            "aps":{
                "sound":"visitor_notification_sound.mp3"
            }
        }
      },  
      "data": {  
         "id": "1215454",  
         "notification_type": "visitor_visited",  
         "other_data_key": "other_data_value"  
     }  
  }  
}

Observations:

  1. With the notification object:

    • Redirection works even when the app is terminated or not in the background.
    • The notification plays the default sound, not the custom sound.
  2. Without the notification object:

    • The custom sound works fine.
    • Redirection fails when the app is terminated or not in the background (click events are not received).
  3. Including both notification and custom sound in the android section:

    • Two notifications are received:
      • One with the default sound.
      • Another with the custom sound.

Flutter Code: Here’s how I’m handling notifications in my Flutter app:

main.dart

import 'package:flutter/material.dart';
import 'package:notification_demo/fcm_controller.dart';
import 'package:notification_demo/firebase_options.dart';

Future<void> main() async {
 WidgetsFlutterBinding.ensureInitialized();
 await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
 await FirebaseCloudMessagingService().initFCM();
 runApp(const MyApp());
}

class MyApp extends StatefulWidget {
 const MyApp({super.key});

 @override
 State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
 @override
 Widget build(BuildContext context) {
   return const MaterialApp(
     home: Scaffold(
       body: Center(
         child: Text('hello'),
       ),
     ),
   );
 }
}

fcm_controller.dart

import 'dart:convert';
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'dart:math' as math;
import 'package:notification_demo/firebase_options.dart';

@pragma('vm:entry-point')
Future<void> notificationTapBackground(NotificationResponse notificationResponse) async {
 debugPrint('background notification tap');
}

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
 await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
 debugPrint("Handling a background message ${message.messageId}");
 if (message.messageId != null && message.messageId!.isNotEmpty) {
   FirebaseCloudMessagingService.showNotification(message);
 }
}

class FirebaseCloudMessagingService {
 Future<void> initFCM() async {
   debugPrint('DDDD initFCM');
   try {
     await _requestPermission();
     await _initNotificationInfo();
     String deviceToken = await _getToken() ?? '';
     debugPrint('FCM Token: $deviceToken');
     await _setupMessageHandlers();
     FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
   } catch (e) {
     log("Exception: $e");
   }
 }

 Future<void> _requestPermission() async {
   NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(
     alert: true,
     announcement: true,
     badge: true,
     carPlay: true,
     criticalAlert: true,
     provisional: true,
     sound: true,
   );
   debugPrint('Permission status: ${settings.authorizationStatus}');
 }

 Future<void> _initNotificationInfo() async {
   var initializationSettingAndroid = const AndroidInitializationSettings('@mipmap/ic_launcher');
   const DarwinInitializationSettings initializationSettingIOS = DarwinInitializationSettings();
   var initializationSettings = InitializationSettings(android: initializationSettingAndroid, iOS: initializationSettingIOS);
   await flutterLocalNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) async {
     handleNotificationTappedFormNotificationTray(jsonDecode(notificationResponse.payload ?? "{}"));
   }, onDidReceiveBackgroundNotificationResponse: notificationTapBackground);
 }

 Future<String?> _getToken() async {
   try {
     return await FirebaseMessaging.instance.getToken();
   } catch (e) {
     debugPrint("Error fetching token: $e");
     return null;
   }
 }

 Future<void> _setupMessageHandlers() async {
   FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
     showNotification(message);
   });
   FirebaseMessaging.onMessageOpenedApp.listen((event) async {
     await handleNotificationTappedFormNotificationTray(event.data);
   });
 }

 static Future<void> showNotification(RemoteMessage message) async {
   String title = message.data['title'] ?? '';
   String body = message.data['body'] ?? '';
   String soundName = 'notification_sound_android';
   String iosSoundName = 'notification_sound_android.mp3';
   if (message.data['notification_type'] == 'visitor_visited') {
     soundName = 'visitor_notification_sound';
     iosSoundName = 'visitor_notification_sound.mp3';
   }
   AndroidNotificationChannel channel = AndroidNotificationChannel(
     soundName,
     'General Notifications',
     importance: Importance.max,
     playSound: true,
     sound: RawResourceAndroidNotificationSound(soundName),
   );
   AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
     channel.id,
     channel.name,
     sound: RawResourceAndroidNotificationSound(soundName),
   );
   NotificationDetails notificationDetails = NotificationDetails(
     android: androidNotificationDetails,
     iOS: DarwinNotificationDetails(sound: iosSoundName),
   );
   flutterLocalNotificationsPlugin.show(
     math.Random().nextInt(100000),
     title,
     body,
     notificationDetails,
     payload: jsonEncode(message.data),
   );
 }

 Future<void> handleNotificationTappedFormNotificationTray(Map<String, dynamic> notificationData) async {
   debugPrint('Notification tapped: $notificationData');
   // Implement redirection logic here
 }
}

Question: How can I configure FCM and handle notifications in Flutter so that:

  1. Custom sound plays without triggering the default sound or duplicating notifications.
  2. Clicking the notification redirects to a specific screen, even if the app is terminated or not running in the background.
  3. Only one notification is displayed.

Is there a way to resolve the conflict between the notification and data objects to achieve the desired behavior?


Solution

  • Well i do have this problems:

    1.Custom sound plays without triggering the default sound or duplicating notifications.

    2.Clicking the notification redirects to a specific screen, even if the app is terminated or not running in the background.

    On flutter_local_notification have a default when clicking the notification goes to the app, try playing this code: NOTE put this with initializing the _flutterLocalNotificationsPlugin1.initialize

    final NotificationAppLaunchDetails? notificationAppLaunchDetails =
        await _flutterLocalNotificationsPlugin1
            .getNotificationAppLaunchDetails();
    final didNotificationLaunchApp =
        notificationAppLaunchDetails?.didNotificationLaunchApp ?? false;
    if (didNotificationLaunchApp == true) {
      /// GET DATA
      final dataX = notificationAppLaunchDetails?.notificationResponse!.payload;
      Future.delayed(const Duration(seconds: 2), () async {
        /// PUT CODE HERE TO REDIRECT
      });
      // throw Exception('$dataX');
    }
    

    as am using go_ router for page navigation as per my code when app is clicked or redirect or scan in qr.

    1. Only one notification is displayed.

    Well on Firebase we have 2 ways to send payload with data and only the notification with body.