You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When using getNotificationAppLaunchDetails() in debug mode, the launch details persist after hot restart, which can cause confusion during development. This appears to be specific to debug mode and hot restart scenarios.
Current Behavior (Debug Mode)
App receives notification in foreground
App is put in background
User taps notification from Notification Center
App opens and processes notification using getNotificationAppLaunchDetails()
Developer performs a hot restart
getNotificationAppLaunchDetails() still returns the previous notification details
Expected Behavior
Even in debug mode with hot restart, getNotificationAppLaunchDetails() should only return notification details when the app is launched directly from a notification tap. After a hot restart, it should return null or indicate that the app wasn't launched from a notification.
Steps to Reproduce (Debug Mode Only)
Run app in debug mode with this notification service:
@singleton
class NotificationService {
final LocalNotificationSetup _localNotificationSetup =
LocalNotificationSetup();
final FirebaseNotificationSetup _firebaseNotificationSetup =
FirebaseNotificationSetup();
/// Initializes both notification systems and sets up message handlers
///
/// Used when: App starts for the first time
/// - Initializes Flutter Local Notifications for foreground messages
/// - Initializes Firebase Messaging for background/terminated messages
/// - Sets up message handlers for different app states
Future initialize() async {
logger.i('NotificationService initialize: Starting initialization');
try {
// Initialize both notification systems
await _localNotificationSetup.init(
onNotificationTap: NotificationNavigation.handleNotificationTap,
);
await _firebaseNotificationSetup.init(
onNotificationTap: NotificationNavigation.handleNotificationTap,
);
/// Sets up handlers for different message scenarios and checks pending notifications
///
/// Handles three scenarios:
/// 1. Foreground messages: Uses Flutter Local Notifications
/// - When: App is open and visible
/// - Action: Shows notification using local notification system
///
/// 2. Background messages: Uses Firebase Messaging
/// - When: App is in background
/// - Action: Firebase shows system notification automatically
///
/// 3. Terminated/Launch messages: Checks both systems
/// - When: App was launched from a notification
/// - Action: Processes pending notifications from both systems
Future _setupMessageHandlers() async {
// Handle foreground messages using Flutter Local Notifications
logger.d(
'NotificationService _setupMessageHandlers: Setting up message handlers');
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
// Check for any pending notifications that might have launched the app
await _checkPendingNotifications();
}
/// Handles messages when app is in foreground
///
/// Used when: App is open and visible to user
/// System: Uses Flutter Local Notifications
/// - Receives message from Firebase
/// - Shows notification using local notification system
/// - Allows custom UI and interaction handling
void _handleForegroundMessage(RemoteMessage message) {
logger.i(
'NotificationService _handleForegroundMessage: Handling foreground message=$message');
// Show local notification when app is in foreground
_localNotificationSetup.showNotification(message);
}
/// Gets the Firebase device token for push notifications
///
/// Used when: App needs to register device for push notifications
/// System: Uses Firebase Messaging
/// - Required for sending targeted notifications to specific devices
/// - Used during user registration or token refresh
Future<String?> getDeviceToken() async {
return await _firebaseNotificationSetup.getToken();
}
/// Subscribes to a specific notification topic
///
/// Used when: App needs to receive notifications for specific topics
/// System: Uses Firebase Messaging
/// - Allows receiving notifications without storing device tokens
/// - Used for broadcast messages to specific groups
Future subscribeToTopic({required String topic}) async {
logger.d(
'NotificationService subscribeToTopic: Subscribing to topic: $topic');
try {
await FirebaseMessaging.instance.subscribeToTopic(topic);
logger.i(
'NotificationService subscribeToTopic: Successfully subscribed to events for institute with topic: $topic');
} catch (e) {
logger.e(
'NotificationService subscribeToTopic: Error subscribing to events: $e');
// rethrow;
}
}
/// Checks for any pending notification requests that might have launched the app
///
/// Used when: App is launched from a terminated state via notification
/// Systems: Checks both Firebase and Flutter Local Notifications
/// - Handles notifications that were shown while app was in foreground
/// - Handles notifications received from Firebase
Future _checkPendingNotifications() async {
logger.d(
'NotificationService _checkPendingNotifications: Checking pending notification requests');
// Check Firebase initial message first
RemoteMessage? firebaseInitial =
await FirebaseMessaging.instance.getInitialMessage();
if (firebaseInitial != null) {
logger.i(
'NotificationService _checkPendingNotifications: Found Firebase initial message: $firebaseInitial');
NotificationNavigation.handleNotificationTap(firebaseInitial);
return;
}
// Check Flutter Local Notifications pending requests
final NotificationAppLaunchDetails? launchDetails =
await _localNotificationSetup.getNotificationAppLaunchDetails();
if (launchDetails != null &&
launchDetails.didNotificationLaunchApp &&
launchDetails.notificationResponse?.payload != null) {
logger.i(
'NotificationService._checkPendingNotifications: Terminated App launched from local notification');
try {
final String payload = launchDetails.notificationResponse!.payload!;
final Map<String, dynamic> data = jsonDecode(payload);
final RemoteMessage message = RemoteMessage(data: data);
logger.i(
'NotificationService._checkPendingNotifications: Handling local notification. message=$message');
NotificationNavigation.handleNotificationTap(message);
} catch (e) {
logger.e(
'NotificationService._checkPendingNotifications: Error processing notification: $e');
}
}
// Always clear notifications at the end of checking
await _localNotificationSetup.clearNotificationDetails();
logger.d(
'NotificationService._checkPendingNotifications: Cleared all notification data');
/// Clears the notification launch details to prevent reprocessing
Future clearNotificationDetails() async {
logger.d(
'LocalNotificationSetup clearNotificationDetails: Starting to clear notifications');
try {
// Cancel all notifications
await _flutterLocalNotificationsPlugin.cancelAll();
// On Android, we need to remove the notification that launched the app
if (Platform.isAndroid) {
final details = await _flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails();
if (details?.notificationResponse?.id != null) {
await _flutterLocalNotificationsPlugin
.cancel(details!.notificationResponse!.id!);
}
}
logger.i(
'LocalNotificationSetup clearNotificationDetails: Successfully cleared notifications');
// Verify clearing
final verifyDetails = await _flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails();
logger.d(
'LocalNotificationSetup clearNotificationDetails: Verification - Launch Details still present: ${verifyDetails?.didNotificationLaunchApp}');
} catch (e) {
logger.e(
'LocalNotificationSetup clearNotificationDetails: Error clearing notifications: $e');
}
}
}
`
Send a test notification while app is in foreground
Background the app
Tap the notification to open the app
Verify notification details are processed
Perform a hot restart
Check getNotificationAppLaunchDetails() - it still returns the previous notification details
Important Notes
This issue only occurs in debug mode
The behavior is specifically related to hot restart
In production builds, this behavior should not occur as we can't do hot-restart
This can cause confusion during development and testing
Environment
flutter_local_notifications version: ^18.0.1
Flutter version: 3.27.1
Platform: Checked in Android
Debug Mode: Yes
Reproduction Method: Hot Restart
Additional Context
We've attempted to clear the notification details using:
cancelAll()
cancel
However, the launch details still persist across hot restarts in debug mode.
Impact
While this shouldn't affect production builds, it makes development and testing of notification-related features more difficult as developers need to fully terminate the app to get accurate notification launch behavior.
Possible Solutions
Clear launch details after hot restart in debug mode
Add a development-time flag to force clear launch details
Add documentation noting this behavior in debug mode
Add a method to explicitly clear launch details that works in debug mode
The text was updated successfully, but these errors were encountered:
Clear launch details after hot restart in debug mode
Seems like there is no hot-restart hook we can listen to. Even Finalizers don't necessarily respond. See flutter/flutter#75528
Add a method to explicitly clear launch details that works in debug mode
This could be good. @MaikuB, would you like a PR for this? Something like plugin.clearLaunchDetails()? I think this is worth fixing: imagine a dev is trying to test a reply or archive button in their notifications. then, hot-restarting would cause their app to try to execute the same logic again.
// Verify clearing
Unrelated, but I'm not sure you need to do this in real applications, though I'd be curious to hear when you would.
Description
When using
getNotificationAppLaunchDetails()
in debug mode, the launch details persist after hot restart, which can cause confusion during development. This appears to be specific to debug mode and hot restart scenarios.Current Behavior (Debug Mode)
getNotificationAppLaunchDetails()
getNotificationAppLaunchDetails()
still returns the previous notification detailsExpected Behavior
Even in debug mode with hot restart,
getNotificationAppLaunchDetails()
should only return notification details when the app is launched directly from a notification tap. After a hot restart, it should return null or indicate that the app wasn't launched from a notification.Steps to Reproduce (Debug Mode Only)
`
//code
import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
import '../my_logger/my_logger.dart';
import 'notification_navigation.dart';
import 'setup/firebase_notification_setup.dart';
import 'setup/local_notification_setup.dart';
@singleton
class NotificationService {
final LocalNotificationSetup _localNotificationSetup =
LocalNotificationSetup();
final FirebaseNotificationSetup _firebaseNotificationSetup =
FirebaseNotificationSetup();
/// Initializes both notification systems and sets up message handlers
///
/// Used when: App starts for the first time
/// - Initializes Flutter Local Notifications for foreground messages
/// - Initializes Firebase Messaging for background/terminated messages
/// - Sets up message handlers for different app states
Future initialize() async {
logger.i('NotificationService initialize: Starting initialization');
try {
// Initialize both notification systems
await _localNotificationSetup.init(
onNotificationTap: NotificationNavigation.handleNotificationTap,
);
await _firebaseNotificationSetup.init(
onNotificationTap: NotificationNavigation.handleNotificationTap,
);
}
/// Sets up handlers for different message scenarios and checks pending notifications
///
/// Handles three scenarios:
/// 1. Foreground messages: Uses Flutter Local Notifications
/// - When: App is open and visible
/// - Action: Shows notification using local notification system
///
/// 2. Background messages: Uses Firebase Messaging
/// - When: App is in background
/// - Action: Firebase shows system notification automatically
///
/// 3. Terminated/Launch messages: Checks both systems
/// - When: App was launched from a notification
/// - Action: Processes pending notifications from both systems
Future _setupMessageHandlers() async {
// Handle foreground messages using Flutter Local Notifications
logger.d(
'NotificationService _setupMessageHandlers: Setting up message handlers');
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
}
/// Handles messages when app is in foreground
///
/// Used when: App is open and visible to user
/// System: Uses Flutter Local Notifications
/// - Receives message from Firebase
/// - Shows notification using local notification system
/// - Allows custom UI and interaction handling
void _handleForegroundMessage(RemoteMessage message) {
logger.i(
'NotificationService _handleForegroundMessage: Handling foreground message=$message');
// Show local notification when app is in foreground
_localNotificationSetup.showNotification(message);
}
/// Gets the Firebase device token for push notifications
///
/// Used when: App needs to register device for push notifications
/// System: Uses Firebase Messaging
/// - Required for sending targeted notifications to specific devices
/// - Used during user registration or token refresh
Future<String?> getDeviceToken() async {
return await _firebaseNotificationSetup.getToken();
}
/// Subscribes to a specific notification topic
///
/// Used when: App needs to receive notifications for specific topics
/// System: Uses Firebase Messaging
/// - Allows receiving notifications without storing device tokens
/// - Used for broadcast messages to specific groups
Future subscribeToTopic({required String topic}) async {
logger.d(
'NotificationService subscribeToTopic: Subscribing to topic: $topic');
}
/// Checks for any pending notification requests that might have launched the app
///
/// Used when: App is launched from a terminated state via notification
/// Systems: Checks both Firebase and Flutter Local Notifications
/// - Handles notifications that were shown while app was in foreground
/// - Handles notifications received from Firebase
Future _checkPendingNotifications() async {
logger.d(
'NotificationService _checkPendingNotifications: Checking pending notification requests');
}
}
`
`
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../../my_logger/my_logger.dart';
import 'notification_setup_base.dart';
class LocalNotificationSetup extends NotificationSetupBase {
static final LocalNotificationSetup _instance =
LocalNotificationSetup._internal();
factory LocalNotificationSetup() => _instance;
LocalNotificationSetup._internal();
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
@OverRide
Future init({Function(RemoteMessage)? onNotificationTap}) async {
logger.d(
'LocalNotificationSetup initialize: Initializing local notifications');
}
void _handleNotificationResponse(
NotificationResponse response, Function(RemoteMessage)? onTap) {
if (response.payload != null) {
try {
final Map<String, dynamic> data = jsonDecode(response.payload!);
final RemoteMessage message = RemoteMessage(data: data);
onTap?.call(message);
} catch (e) {
logger
.e('LocalNotificationSetup _handleNotificationResponse: Error: $e');
}
}
}
@pragma('vm:entry-point')
void _handleBackgroundNotificationResponse(
NotificationResponse response, Function(RemoteMessage)? onTap) {
logger.d(
'LocalNotificationSetup _handleBackgroundNotificationResponse: Handling background notification response');
_handleNotificationResponse(response, onTap);
}
@OverRide
Future showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
}
@OverRide
Future<String?> getToken() async =>
null; // Not applicable for local notifications
Future<NotificationAppLaunchDetails?>
getNotificationAppLaunchDetails() async {
return await _flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails();
}
/// Clears the notification launch details to prevent reprocessing
Future clearNotificationDetails() async {
logger.d(
'LocalNotificationSetup clearNotificationDetails: Starting to clear notifications');
}
}
`
getNotificationAppLaunchDetails()
- it still returns the previous notification detailsImportant Notes
Environment
Additional Context
We've attempted to clear the notification details using:
cancelAll()
cancel
However, the launch details still persist across hot restarts in debug mode.
Impact
While this shouldn't affect production builds, it makes development and testing of notification-related features more difficult as developers need to fully terminate the app to get accurate notification launch behavior.
Possible Solutions
The text was updated successfully, but these errors were encountered: