Flutter Firebase通知点击无法跳转指定页面问题排查
Flutter Firebase通知点击无法跳转指定页面问题
我开发了一款Flutter应用,集成Firebase与flutter_local_notifications插件实现通知功能。目前应用能正常接收通知,点击通知可打开应用,但无法跳转到预期的指定页面。已简化代码排查问题,可能存在遗漏点,相关代码如下:
firebase_notification.helper.dart
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest_all.dart' as timezone; import 'package:timezone/timezone.dart' as timezone; class FirebaseNotificationHelper { static final _notification = FlutterLocalNotificationsPlugin(); static void init() { _notification.initialize( const InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), iOS: DarwinInitializationSettings(), ), ); } static pushNotification( RemoteMessage message, ) async { var androidPlatformChannelSpecifics = const AndroidNotificationDetails( 'channel id', 'channel name', channelDescription: 'channel description', importance: Importance.max, priority: Priority.high, ); var iOSPlatformChannelSpecifics = const DarwinNotificationDetails(); var platformChannelSpecifics = NotificationDetails( android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics, ); await _notification.show(1, message.notification?.title, message.notification?.body, platformChannelSpecifics); } }
firebase.helper.dart
import 'dart:convert'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:grocstock/firebase_options.dart'; import 'package:grocstock/helpers/firebase_notification.helper.dart'; import 'package:grocstock/helpers/firestore.helper.dart'; import 'package:grocstock/helpers/page.helper.dart'; import 'package:grocstock/helpers/popup.helper.dart'; import 'package:grocstock/helpers/storage.helper.dart'; import 'package:grocstock/models/logged.user.model.dart'; import 'package:grocstock/modules/home/home.dart'; import 'package:grocstock/modules/preview_home/preview_home.dart'; import 'package:grocstock/services/house.service.dart'; initializeFirebase(BuildContext context) async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); FirebaseNotificationHelper.init(); listenToMessages(context); } initFirebaseMessaging() async { FirebaseMessaging messaging = FirebaseMessaging.instance; try { String? token = await FirebaseMessaging.instance.getToken(); const storage = FlutterSecureStorage(); storage.write(key: 'fcmToken', value: token); await initFireBaseHouseUser(token); } catch (e) { print('Failed to get FCM token: $e'); } await messaging.requestPermission(); } listenToMessages(BuildContext context) { FirebaseMessaging.onMessage.listen((RemoteMessage message) async { if (message.notification != null) { print('Message also contained a notification: ${message.data}'); } bool notificationsEnabled = await checkAppPopUpPermission(); bool actionNotificationsEnabled = await checkActionAppPopUpPermission(); bool messageNotificationsEnabled = await checkMessageAppPopUpPermission(); if (!notificationsEnabled) return; String type = message.data['type']; bool showNotification = true; if (type == 'ACTION' && !actionNotificationsEnabled) { showNotification = false; } else if (type == 'MESSAGE' && !messageNotificationsEnabled) { showNotification = false; } if (showNotification) { await FirebaseNotificationHelper.pushNotification(message); } }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async { await _redirectToHouse(context, message); }); FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) { if (message != null) { print( 'App opened from terminated state by notification with data: ${message.data}'); _redirectToHouse(context, message); } }); } Future<void> _redirectToHouse(BuildContext context, RemoteMessage message) async { try { String? houseId = message.data['houseId']; if(houseId != null && houseId.isNotEmpty) { var house = await getHouse(context, houseId); var user = await safeReadFromStorage('user', null); var loggedUser = LoggedUser.fromJson(jsonDecode(user!)); String isAdmin = message.data['isAdmin']; if(isAdmin == 'true') { print('Navigate to Saved house'); redirectToPage(context, PreviewHomePage(house: house, user: loggedUser)); } else { redirectToPage(context, HomePage(house: house, user: loggedUser)); print('Navigate to actual house'); } } } catch (e) { print(e); } }
splash.page.dart
class SplashScreenState extends State<SplashScreen> { static final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: 'Main Navigator'); DarkThemeProvider themeChangeProvider = DarkThemeProvider(); @override void initState() { super.initState(); _initializeFirebase(); } _initializeFirebase() async { await initializeFirebase(context); } Future<bool> _checkTermsAccepted() async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getBool('termsAccepted') ?? false; } @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) { return themeChangeProvider; }, child: Consumer<DarkThemeProvider>( builder: (BuildContext builderContext, DarkThemeProvider value, Widget? child) { return FutureBuilder<bool>( future: _checkTermsAccepted(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else { bool termsAccepted = snapshot.data ?? false; return MaterialApp( navigatorKey: navigatorKey, title: 'Welcome', debugShowCheckedModeBanner: false, theme: Styles.themeData( themeChangeProvider.darkTheme, builderContext), home: termsAccepted ? Login(shouldRedirect: true) : const TermsAndConditionsPage(), onGenerateRoute: generateRoute, builder: BotToastInit(), navigatorObservers: [BotToastNavigatorObserver()], ); } }, ); }, ), ); } }
问题场景
当前APP在以下三种场景下均无法正常跳转:
- 应用完全关闭时
- 应用处于后台(最小化)时
- 应用处于前台时
依赖版本
flutter_local_notifications: ^17.2.1+2 firebase_core: ^3.4.0 cloud_firestore: ^5.3.0 firebase_messaging: ^15.1.0
问题分析与修复方案
1. FlutterLocalNotifications未处理前台通知点击回调
应用前台收到通知时,点击flutter_local_notifications展示的通知不会触发Firebase的消息回调,必须手动配置插件的点击回调,并传递消息数据。
修复:
修改FirebaseNotificationHelper类,添加点击回调并传入消息数据:
import 'dart:convert'; // 新增导入 class FirebaseNotificationHelper { static final _notification = FlutterLocalNotificationsPlugin(); static void init() { _notification.initialize( const InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), iOS: DarwinInitializationSettings(), ), // 新增通知点击回调 onDidReceiveNotificationResponse: (NotificationResponse response) async { if (response.payload != null) { final Map<String, dynamic> data = jsonDecode(response.payload!); await _handleNotificationTap(data); } }, ); } static pushNotification(RemoteMessage message) async { var androidPlatformChannelSpecifics = const AndroidNotificationDetails( 'channel id', 'channel name', channelDescription: 'channel description', importance: Importance.max, priority: Priority.high, ); var iOSPlatformChannelSpecifics = const DarwinNotificationDetails(); var platformChannelSpecifics = NotificationDetails( android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics, ); // 将消息data转为json字符串作为payload传递 String payload = jsonEncode(message.data); await _notification.show( 1, message.notification?.title, message.notification?.body, platformChannelSpecifics, payload: payload, ); } // 统一处理通知点击跳转 static Future<void> _handleNotificationTap(Map<String, dynamic> data) async { try { String? houseId = data['houseId']; if (houseId != null && houseId.isNotEmpty) { var house = await getHouse(SplashScreenState.navigatorKey.currentContext!, houseId); var user = await safeReadFromStorage('user', null); var loggedUser = LoggedUser.fromJson(jsonDecode(user!)); String isAdmin = data['isAdmin']; Widget page = isAdmin == 'true' ? PreviewHomePage(house: house, user: loggedUser) : HomePage(house: house, user: loggedUser); // 使用全局navigatorKey跳转 SplashScreenState.navigatorKey.currentState?.push( MaterialPageRoute(builder: (_) => page), ); } } catch (e) { print(e); } } }
2. 全局NavigatorKey使用不规范
应用从终止状态启动时,传入的context可能未完成初始化,必须使用全局navigatorKey处理所有跳转场景。
修复:
修改firebase.helper.dart中的监听方法,移除context依赖,统一使用全局navigatorKey:
// 修改initializeFirebase,移除context参数 initializeFirebase() async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); FirebaseNotificationHelper.init(); listenToMessages(); } // 修改listenToMessages,移除context参数 listenToMessages() { FirebaseMessaging.onMessage.listen((RemoteMessage message) async { // 原有逻辑不变 }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async { await _redirectToHouse(message); }); FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) { if (message != null) { print('App opened from terminated state by notification with data: ${message.data}'); _redirectToHouse(message); } }); } // 修改_redirectToHouse,使用全局navigatorKey Future<void> _redirectToHouse(RemoteMessage message) async { try { String? houseId = message.data['houseId']; if(houseId != null && houseId.isNotEmpty) { var house = await getHouse(SplashScreenState.navigatorKey.currentContext!, houseId); var user = await safeReadFromStorage('user', null); var loggedUser = LoggedUser.fromJson(jsonDecode(user!)); String isAdmin = message.data['isAdmin']; Widget page = isAdmin == 'true' ? PreviewHomePage(house: house, user: loggedUser) : HomePage(house: house, user: loggedUser); // 确保navigator已初始化,未就绪则延迟执行 if (SplashScreenState.navigatorKey.currentState != null) { SplashScreenState.navigatorKey.currentState?.push( MaterialPageRoute(builder: (_) => page), ); } else { Future.delayed(const Duration(milliseconds: 500), () { SplashScreenState.navigatorKey.currentState?.push( MaterialPageRoute(builder: (_) => page), ); }); } } } catch (e) { print(e); } }
同时修改splash.page.dart中的初始化方法:
_initializeFirebase() async { await initializeFirebase(); // 不再传context }
3. 路由跳转逻辑校验
检查redirectToPage方法,确保其使用全局navigatorKey而非传入的context,如果是自定义跳转方法,建议替换为直接使用navigatorKey的跳转逻辑。
额外注意事项
- 打印
message.data确认通知中确实包含houseId和isAdmin参数; - iOS平台需在
Info.plist中配置远程通知权限,确保FirebaseMessaging权限请求成功; - 测试终止状态跳转时,需完全关闭应用(后台划掉)后再点击通知测试。
内容的提问来源于stack exchange,提问作者Rohith Poyyeri
相关产品推荐
相关产品推荐

