-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Description
Is there an existing issue for this?
- I have searched the existing issues.
Which plugins are affected?
Messaging
Which platforms are affected?
Web
Description
When a Firebase Cloud Messaging (FCM) notification is clicked while the Flutter Web app is already in the foreground (with focus), the app loses focus after navigation. As a result, the user cannot interact with any UI elements — the mouse pointer does not switch to the clickable "hand" cursor, and clicks are ignored.
If the browser window is minimized and then reopened, the app regains focus and interaction works again. Similarly, when the notification is clicked while the app is in the background, navigation and interaction behave as expected.
Expected behavior:
Clicking a notification should navigate correctly without causing the Flutter Web app to lose focus or block user interaction.
Actual behavior:
After clicking a notification with the app in the foreground, the app loses focus, preventing any interaction until the window is minimized and reopened.
Reproducing the issue
- Configure Firebase in your Flutter Web project to send notifications. Remember to generate your
vapidKey
to obtain the token. - Run the app.
- Accept the permissions to receive notifications.
- Copy the device token printed in the console.
- From the Firebase Console, send a test notification using the copied token.
- Make sure the app window is in focus.
- Click the notification.
- Press the back arrow button inside the Flutter app.
Video:
Screen.Recording.2025-10-05.at.7.24.33.AM.mov
Firebase Core version
4.1.1
Flutter Version
3.35.3
Relevant Log Output
flutter doctor -v
[✓] Flutter (Channel stable, 3.35.3, on macOS 26.0.1 25A362 darwin-arm64, locale en-US) [3.3s]
• Flutter version 3.35.3 on channel stable at /Users/andry/fvm/versions/3.35.3
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision a402d9a437 (5 weeks ago), 2025-09-03 14:54:31 -0700
• Engine revision ddf47dd3ff
• Dart version 3.9.2
• DevTools version 2.48.0
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
enable-lldb-debugging
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1) [7.0s]
• Android SDK at /Users/andry/Library/Android/sdk
• Emulator version 35.4.9.0 (build_id 13025442) (CL:N/A)
• Platform android-36, build-tools 35.0.1
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.5+-13047016-b750.29)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 16.3) [2.1s]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 16E140
• CocoaPods version 1.16.2
[✓] Chrome - develop for the web [6ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2024.3) [5ms]
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 21.0.5+-13047016-b750.29)
[✓] VS Code (version 1.104.1) [4ms]
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.120.0
[✓] Connected device (2 available) [5.8s]
• macOS (desktop) • macos • darwin-arm64 • macOS 26.0.1 25A362 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 141.0.7390.54
! Error: Browsing on the local area network for Andrys’s Phone. Ensure the device is unlocked and attached with a cable or associated with the same local
area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
! Error: Browsing on the local area network for iPhone. Ensure the device is unlocked and attached with a cable or associated with the same local area
network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
! Error: Browsing on the local area network for Andrys’s Phone. Ensure the device is unlocked and attached with a cable or associated with the same local
area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources [1,063ms]
• All expected network resources are available.
! Doctor found issues in 1 category.
Flutter dependencies
Expand Flutter dependencies
snippet
Dart SDK 3.9.2
Flutter SDK 3.35.3
flutter_application_1 0.1.0
dependencies:
- firebase_core 4.1.1 [firebase_core_platform_interface firebase_core_web flutter meta]
- firebase_messaging 16.0.2 [firebase_core firebase_core_platform_interface firebase_messaging_platform_interface firebase_messaging_web flutter meta]
- flutter 0.0.0 [characters collection material_color_utilities meta vector_math sky_engine]
- web 1.1.1
dev dependencies:
- flutter_lints 5.0.0 [lints]
- flutter_test 0.0.0 [flutter test_api matcher path fake_async clock stack_trace vector_math leak_tracker_flutter_testing collection meta stream_channel]
transitive dependencies:
- _flutterfire_internals 1.3.62 [collection firebase_core firebase_core_platform_interface flutter meta]
- async 2.12.0 [collection meta]
- boolean_selector 2.1.2 [source_span string_scanner]
- characters 1.4.0
- clock 1.1.2
- collection 1.19.1
- fake_async 1.3.3 [clock collection]
- firebase_core_platform_interface 6.0.1 [collection flutter flutter_test meta plugin_platform_interface]
- firebase_core_web 3.1.1 [firebase_core_platform_interface flutter flutter_web_plugins meta web]
- firebase_messaging_platform_interface 4.7.2 [_flutterfire_internals firebase_core flutter meta plugin_platform_interface]
- firebase_messaging_web 4.0.2 [_flutterfire_internals firebase_core firebase_core_web firebase_messaging_platform_interface flutter flutter_web_plugins meta web]
- flutter_web_plugins 0.0.0 [flutter]
- leak_tracker 11.0.1 [clock collection meta path vm_service]
- leak_tracker_flutter_testing 3.0.10 [flutter leak_tracker leak_tracker_testing matcher meta]
- leak_tracker_testing 3.0.2 [leak_tracker matcher meta]
- lints 5.1.1
- matcher 0.12.17 [async meta stack_trace term_glyph test_api]
- material_color_utilities 0.11.1 [collection]
- meta 1.16.0
- path 1.9.1
- plugin_platform_interface 2.1.8 [meta]
- sky_engine 0.0.0
- source_span 1.10.1 [collection path term_glyph]
- stack_trace 1.12.1 [path]
- stream_channel 2.1.4 [async]
- string_scanner 1.4.1 [source_span]
- term_glyph 1.2.2
- test_api 0.7.6 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph]
- vector_math 2.2.0
- vm_service 14.3.1
Additional context and comments
Relevant files
main.dart
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:web/web.dart' as web;
import 'package:flutter/material.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void _listenNotification() {
final swContainer = web.window.navigator.serviceWorker;
try {
swContainer.startMessages();
} catch (_) {}
swContainer.addEventListener(
'message',
((JSAny? event) {
final jsObj = event as JSObject?;
final raw = jsObj?.getProperty('data'.toJS);
if (!raw.isA<JSString>()) return;
if ((raw as JSString).toDart == 'notification-click') {
navigatorKey.currentState?.pushNamed('/notificationScreen');
}
}).toJS,
);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
_listenNotification();
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: '...',
appId: '...',
messagingSenderId: '...',
projectId: '...',
authDomain: '...',
storageBucket: '...',
measurementId: '...',
),
);
// Fetch notification token
final token = await FirebaseMessaging.instance.getToken(
vapidKey:
'...',
);
print("FCM Token: $token");
runApp(const NavigatorExampleApp());
}
class NavigatorExampleApp extends StatelessWidget {
const NavigatorExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
navigatorKey: navigatorKey,
routes: <String, WidgetBuilder>{
'/notificationScreen': (BuildContext context) =>
const NotificationScreen(),
},
title: 'Navigator Example',
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Home Page', style: Theme.of(context).textTheme.headlineMedium),
ElevatedButton(
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/notificationScreen');
},
child: const Text('Go to Notification Screen'),
),
],
),
);
}
}
class NotificationScreen extends StatelessWidget {
const NotificationScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Notification Screen',
style: Theme.of(context).textTheme.headlineMedium,
),
IconButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
),
],
),
);
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_application_1">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>Notification Example</title>
<link rel="manifest" href="manifest.json">
<script>
// Tokens that the build step will substitute:
// When building your flutter app, the flutter build web command produces a script called flutter_bootstrap.js in the output build directory (build/web).
// This file contains the JavaScript code needed to initialize and run your Flutter app.
// The entire contents of the `flutter_bootstrap.js` file can be inlined by inserting this template token
{{flutter_bootstrap_js}}
// A unique number representing the build version of the service worker
{{flutter_service_worker_version}}
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer=""></script>
</head>
<body>
<script>
// Register the Service Worker for Firebase messaging with a specific scope.
navigator?.serviceWorker.register('firebase-messaging-sw.js', {
// The scope defines the range of URLs that the service worker will control.
// In this case, it is set to '/firebase-cloud-messaging-push-scope', meaning
// the service worker will handle requests within this path.
scope: '/firebase-cloud-messaging-push-scope',
});
</script>
</body>
</html>
firebase-messaging-sw.js
self.addEventListener("push", function (event) {
let payload = {};
try { payload = event.data.json(); } catch (_) {}
const title = payload.notification?.title;
const options = {
body: payload.notification?.body,
data: payload,
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener("notificationclick", function (event) {
event.notification.close();
event.waitUntil(
clients.matchAll({ type: "window", includeUncontrolled: true }).then(function (clientList) {
if (clientList.length > 0) {
clientList[0].postMessage('notification-click');
return clientList[0].focus();
}
})
);
});
importScripts("https://www.gstatic.com/firebasejs/10.11.1/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.11.1/firebase-messaging-compat.js");
firebase.initializeApp({
apiKey: '...',
appId: '...',
messagingSenderId: '...',
projectId: '...',
authDomain: '...',
storageBucket: '...',
measurementId: '...',
});
const messaging = firebase.messaging();