I am working on integrating FCM notifications into my Flutter app but haven't found many examples on displaying the notification details in a widget (plenty of documentation for printing notification data to the console). I pieced the below together from various tutorials, but there are two problems: No notification is received when the app has foreground, and when a background notification is received, tapping it launches the app but without navigating to the NotificationWidget
as expected:
// firebase_api.dart
Future<void> handleBackgroundMessage(RemoteMessage? message) async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
if (message == null) return;
print("Message received: ${message.data}");
navigatorKey.currentState?.pushNamed(
NotificationWidget.route,
arguments: message,
);
}
class FirebaseApi {
static final _firebaseMessaging = FirebaseMessaging.instance;
static const _androidChannel = AndroidNotificationChannel(
'my_app_notifications',
'My App Notifications',
description: 'My App Notifications',
importance: Importance.defaultImportance,
);
final _localNotifications = FlutterLocalNotificationsPlugin();
static void handleMessage(RemoteMessage? message) {
if (message == null) return;
print("Message received: ${message.data}");
navigatorKey.currentState?.pushNamed(
NotificationWidget.route,
arguments: message,
);
}
Future initPushNotifications() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.instance.getInitialMessage().then(handleMessage);
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
FirebaseMessaging.onMessage.listen((event) {
final notification = event.notification;
if (notification == null) return;
_localNotifications.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
_androidChannel.id,
_androidChannel.name,
channelDescription: _androidChannel.description,
icon: 'assets/image.png',
priority: Priority.high,
),
iOS: const DarwinNotificationDetails()),
payload: jsonEncode(event.toMap()),
);
});
}
Future initLocalNotifications() async {
const iOS = DarwinInitializationSettings();
const android = AndroidInitializationSettings('assets/image.png');
const settings = InitializationSettings(android: android, iOS: iOS);
await _localNotifications.initialize(
settings,
onDidReceiveNotificationResponse: (payload) {
final message = RemoteMessage.fromMap(jsonDecode(payload as String));
handleMessage(message);
},
);
final platform = _localNotifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(_androidChannel);
}
Future<void> initNotifications() async {
await _firebaseMessaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
initPushNotifications();
initLocalNotifications();
print("FirebaseApi was initialized");
}
}
//notification.dart
class NotificationWidget extends StatelessWidget {
const NotificationWidget({Key? key}) : super(key: key);
static const route = '/NotificationWidget';
@override
Widget build(BuildContext context) {
final RemoteMessage message = ModalRoute.of(context)!.settings.arguments as RemoteMessage;
return Scaffold(
appBar: AppBar(
title: const Text("Message From My App"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Text('${message.notification?.title}'),
Text('${message.notification?.body}'),
Text('${message.data}'),
const Spacer(),
],
),
),
);
}
}
// main.dart
final navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
var firebaseApi = FirebaseApi();
await firebaseApi.initNotifications();
await firebaseApi.initLocalNotifications();
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
audioHandler = await AudioService.init(
builder: () => AudioPlayerHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId:
'com.me.my_app_app.channel.audio',
androidNotificationChannelName: 'My App',
androidNotificationOngoing: true,
),
);
runApp(const MyApp());
}
AndroidManifest.xml
<!-- Outside of activity tags but inside application tags -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="my_app_notifications" />
<!-- Inside of activity tags -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Notably, the same android notification channel used by both AudioService
and FirebaseApi
. This seems unrelated to the specified route not displaying when a user taps the navigation.