I'm using Firebase Cloud Messaging (FCM) in my Flutter app and trying to achieve the following:
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:
Custom Sound Problem:
Redirection Problem:
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:
With the notification object:
Without the notification object:
Including both notification and custom sound in the android section:
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:
Is there a way to resolve the conflict between the notification and data objects to achieve the desired behavior?
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.
Well on Firebase we have 2 ways to send payload with data and only the notification with body.