From e30f5dabcc0f17a51b6fe8a05fc5566b36d6ba8c Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Tue, 21 Apr 2026 17:57:53 +0200 Subject: [PATCH] fix(notifications): handle deep linking on cold start without crash --- apps/mobile_app/lib/core/init_app.dart | 1 + .../lib/core/notifications_init.dart | 30 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/mobile_app/lib/core/init_app.dart b/apps/mobile_app/lib/core/init_app.dart index ab017957..0ed4f783 100644 --- a/apps/mobile_app/lib/core/init_app.dart +++ b/apps/mobile_app/lib/core/init_app.dart @@ -38,6 +38,7 @@ Future initApp(EnvironmentEnum env) async { initSfTracking(); configureAppRouter(); + onRouterReady(); // TODO Fase 2: await initSentry(env); diff --git a/apps/mobile_app/lib/core/notifications_init.dart b/apps/mobile_app/lib/core/notifications_init.dart index 9da47413..3fbe2b1f 100644 --- a/apps/mobile_app/lib/core/notifications_init.dart +++ b/apps/mobile_app/lib/core/notifications_init.dart @@ -6,15 +6,6 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:navigation/navigation.dart'; import 'package:sf_app_platform/navigation/app_router.dart'; -/// Background message handler. MUST be a top-level function annotated with -/// `@pragma('vm:entry-point')` so the Flutter engine can dispatch it from a -/// background isolate when the app is terminated or backgrounded. -/// -/// This runs in a separate isolate: it CANNOT access main-isolate state -/// (providers, GetIt, navigation). Keep it side-effect free or schedule work -/// via shared_preferences. Do not call `Firebase.initializeApp` here — -/// firebase_messaging 14+ auto-initializes the default app for the background -/// isolate. @pragma('vm:entry-point') Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { debugPrint('[FCM-bg] messageId: ${message.messageId}'); @@ -30,6 +21,9 @@ const String _localChannelDescription = final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin(); +Map? _pendingNotificationData; +bool _routerReady = false; + Future setupNotifications() async { final messaging = FirebaseMessaging.instance; @@ -66,10 +60,19 @@ Future setupNotifications() async { } } +void onRouterReady() { + _routerReady = true; + final pending = _pendingNotificationData; + if (pending != null) { + _pendingNotificationData = null; + _handleNotificationNavigation(pending); + } +} + Future _initLocalNotifications() async { const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher'); const iosInit = DarwinInitializationSettings( - requestAlertPermission: false, // already requested via FirebaseMessaging + requestAlertPermission: false, requestBadgePermission: false, requestSoundPermission: false, ); @@ -83,7 +86,6 @@ Future _initLocalNotifications() async { onDidReceiveNotificationResponse: _onLocalNotificationTapped, ); - // Android 8+ requires every notification to belong to a channel. const channel = AndroidNotificationChannel( _localChannelId, _localChannelName, @@ -153,6 +155,12 @@ void _onLocalNotificationTapped(NotificationResponse response) { } void _handleNotificationNavigation(Map data) { + if (!_routerReady) { + _pendingNotificationData = data; + debugPrint('[Notification] router not ready, queued for later'); + return; + } + final currentLocation = appRouter.routerDelegate.currentConfiguration.uri.path; if (!currentLocation.startsWith(AppRoutes.legacyDashboard)) return;