Flutter FCM通知跳转页面时触发已销毁Widget祖先查找异常求助
问题:FCM通知跳转时触发"Looking up a deactivated widget's ancestor is unsafe"异常
我实现了单例的PushNotificationsManager类监听Firebase FCM通知,根据逻辑打开指定页面。在HomePage的initState中传入BuildContext初始化该类。
当点击前台通知弹窗或从后台点击通知唤醒应用时,调用navigateToChannelDetailsPage方法获取HomeCubit并跳转页面,此时触发异常:Looking up a deactivated widget's ancestor is unsafe。
HomePage初始化代码
class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); PushNotificationsManager().listen(context); }
main.dart代码
Future<void> backgroundHandler(RemoteMessage message) async { print('Handling a background message ${message.messageId}'); print('Content of message: ' + message.toString()); } Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); configureInjections(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); FirebaseMessaging.onBackgroundMessage(backgroundHandler); runApp(SayApp()); }
PushNotificationsManager核心代码
@injectable class PushNotificationsManager { static final PushNotificationsManager _instance = PushNotificationsManager._internal(); factory PushNotificationsManager() { return _instance; } PushNotificationsManager._internal(); Future<void> listen(BuildContext context) async { if (Platform.isIOS) { NotificationSettings settings = await FirebaseMessaging.instance.requestPermission( alert: true, badge: true, provisional: false, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.authorized) { print('User granted permission'); await _handleNotifications(context); } } else { await _handleNotifications(context); } } Future<void> _handleNotifications(BuildContext context) async { // 应用冷启动时的通知 FirebaseMessaging.instance.getInitialMessage().then((message) { if (message != null) { openNotification(message, context); } }); // 前台通知 FirebaseMessaging.onMessage.listen((message) { if (message.notification != null) { _displayInAppNotifications(message); } }); // 后台唤醒应用时的通知 FirebaseMessaging.onMessageOpenedApp.listen((message) { openNotification(message, context); }); } void _displayInAppNotifications(RemoteMessage message) { FlutterRingtonePlayer.playNotification(); showOverlayNotification((context) { return GestureDetector( onTap: () { openNotification(message, context); OverlaySupportEntry.of(context)!.dismiss(); }, child: SafeArea( child: Card( child: ListTile( leading: message.data[NotificationType.NOTIFICATION_TYPE] == NotificationType.PRIVATE_MESSAGE_1_ON_1 ? Avatar(text: message.notification!.title!) : SizedBox.fromSize( size: const Size(40, 40), child: ClipOval( child: Container( child: SvgPicture.asset(SvgIcons.sayAppWaveLogo), ))), title: Text(message.notification!.title!, overflow: TextOverflow.ellipsis, maxLines: 1), subtitle: Text( message.notification!.body!, overflow: TextOverflow.ellipsis, maxLines: 2, ), trailing: IconButton( icon: Icon(Icons.close), onPressed: () { OverlaySupportEntry.of(context)!.dismiss(); }), ), ), ), ); }, duration: Duration(seconds: 4), position: NotificationPosition.top); } Future<void> navigateToChannelDetailsPage(ChannelEntity channel, BuildContext context) async { var cubit = BlocProvider.of<HomeCubit>(context); var isUserChannelMember = await cubit.isCurrentUserChannelMember(channel); if (!isUserChannelMember) return; var isUserMutedPublic = await cubit.getIsUserMutedPublic(channel); if (isUserMutedPublic) channel.isUserMutedPublic = isUserMutedPublic; return PageNavigator.navigate( context, ChannelDetailsPage(channel: channel, isFromAdminNotification: true)) .then((_) { FocusManager.instance.primaryFocus?.unfocus(); cubit.updateChannelMemberChatDetailsDataOnChannelClosed(channel); }); }
异常信息
E/flutter (25412): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe. E/flutter (25412): At this point the state of the widget's element tree is no longer stable. E/flutter (25412): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method. E/flutter (25412): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:4241:9) E/flutter (25412): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4255:6) E/flutter (25412): #2 Element.getElementForInheritedWidgetOfExactType (package:flutter/src/widgets/framework.dart:4286:12) E/flutter (25412): #3 Provider._inheritedElementOf (package:provider/src/provider.dart:339:38) E/flutter (25412): #4 Provider.of (package:provider/src/provider.dart:293:30) E/flutter (25412): #5 BlocProvider.of (package:flutter_bloc/src/bloc_provider.dart:100:23) E/flutter (25412): #6 PushNotificationsManager.navigateToChannelDetailsPage (package:say_app/presentation/notification/push_notifications_manager.dart:204:30) E/flutter (25412): #7 PushNotificationsManager.openNotification (package:say_app/presentation/notification/push_notifications_manager.dart:180:9) E/flutter (25412): <asynchronous suspension>
解决方案
异常核心原因:单例PushNotificationsManager持有了HomePage的BuildContext,当HomePage被销毁(如页面跳转后回收),该context已失效,此时用它获取BlocProvider就会触发错误。
具体修复步骤:
移除单例对Widget BuildContext的持有
修改listen和_handleNotifications方法,去掉context参数,不再将页面上下文保存到单例中。配置全局导航Key
在main.dart中定义全局GlobalKey<NavigatorState>,用来获取当前有效的导航上下文:final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); configureInjections(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); FirebaseMessaging.onBackgroundMessage(backgroundHandler); runApp(SayApp(navigatorKey: navigatorKey)); }在
SayApp中将该Key传入MaterialApp:class SayApp extends StatelessWidget { final GlobalKey<NavigatorState> navigatorKey; const SayApp({required this.navigatorKey, Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( navigatorKey: navigatorKey, // 其他MaterialApp配置项 ); } }修改PushNotificationsManager的上下文获取逻辑
通过全局导航Key获取当前活跃上下文,同时在异步操作前后检查上下文状态:// 引入全局navigatorKey,根据实际路径调整import import '../main.dart'; @injectable class PushNotificationsManager { static final PushNotificationsManager _instance = PushNotificationsManager._internal(); factory PushNotificationsManager() { return _instance; } PushNotificationsManager._internal(); Future<void> listen() async { if (Platform.isIOS) { NotificationSettings settings = await FirebaseMessaging.instance.requestPermission( alert: true, badge: true, provisional: false, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.authorized) { print('User granted permission'); await _handleNotifications(); } } else { await _handleNotifications(); } } Future<void> _handleNotifications() async { FirebaseMessaging.instance.getInitialMessage().then((message) { if (message != null) { openNotification(message); } }); FirebaseMessaging.onMessage.listen((message) { if (message.notification != null) { _displayInAppNotifications(message); } }); FirebaseMessaging.onMessageOpenedApp.listen((message) { openNotification(message); }); } void _displayInAppNotifications(RemoteMessage message) { FlutterRingtonePlayer.playNotification(); showOverlayNotification((overlayContext) { return GestureDetector( onTap: () { openNotification(message); OverlaySupportEntry.of(overlayContext)!.dismiss(); }, child: SafeArea( child: Card( child: ListTile( leading: message.data[NotificationType.NOTIFICATION_TYPE] == NotificationType.PRIVATE_MESSAGE_1_ON_1 ? Avatar(text: message.notification!.title!) : SizedBox.fromSize( size: const Size(40, 40), child: ClipOval( child: Container( child: SvgPicture.asset(SvgIcons.sayAppWaveLogo), ))), title: Text(message.notification!.title!, overflow: TextOverflow.ellipsis, maxLines: 1), subtitle: Text( message.notification!.body!, overflow: TextOverflow.ellipsis, maxLines: 2, ), trailing: IconButton( icon: Icon(Icons.close), onPressed: () { OverlaySupportEntry.of(overlayContext)!.dismiss(); }), ), ), ), ); }, duration: Duration(seconds: 4), position: NotificationPosition.top); } Future<void> navigateToChannelDetailsPage(ChannelEntity channel) async { final context = navigatorKey.currentContext; if (context == null || !context.mounted) return; var cubit = BlocProvider.of<HomeCubit>(context); var isUserChannelMember = await cubit.isCurrentUserChannelMember(channel); if (!isUserChannelMember) return; var isUserMutedPublic = await cubit.getIsUserMutedPublic(channel); if (isUserMutedPublic) channel.isUserMutedPublic = isUserMutedPublic; if (!context.mounted) return; return PageNavigator.navigate( context, ChannelDetailsPage(channel: channel, isFromAdminNotification: true)) .then((_) { FocusManager.instance.primaryFocus?.unfocus(); if (context.mounted) { cubit.updateChannelMemberChatDetailsDataOnChannelClosed(channel); } }); } }更新HomePage的初始化代码
调用listen时不再传入context:class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); PushNotificationsManager().listen(); } }
额外注意事项:
- 所有异步操作完成后,必须检查
context.mounted,避免在已销毁的上下文上执行操作。 - 禁止在单例或长期存活对象中持有Widget的
BuildContext,Widget生命周期短暂,上下文随时可能失效。 - 若使用依赖注入框架(如get_it),可将
navigatorKey注册为单例,让PushNotificationsManager通过注入获取,无需直接引用main.dart。
内容的提问来源于stack exchange,提问作者Zookey
相关产品推荐
相关产品推荐

