You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter应用冷启动无法读取Firebase通知数据 后台状态可正常读取

Flutter 冷启动点击Firebase通知无法读取数据跳转的解决方案

问题根因

  • FirebaseMessaging.onMessageOpenedApp 流仅会在应用处于后台存活状态、点击通知唤起时触发,完全终止的冷启动场景不会走这个回调,这是现有代码漏处理的核心场景
  • 现有代码存在异步时序问题:FirebaseNotification.init() 调用后,没有等待冷启动场景的初始通知获取完成就直接启动App,同时全局导航key对应的Navigator还没完成挂载,就算拿到数据也会因为currentState为空报错
  • 现有payload解析逻辑用字符串替换分割的写法非常脆弱,通知字段里如果带冒号、逗号就会解析失败

具体修改方案

1. 补充冷启动场景初始通知获取逻辑

首先修改firebase_notification.dart,调整init方法返回值为Future,新增冷启动初始消息的获取逻辑,同时把拿到的初始消息存为全局可访问变量,避免App没加载完就执行跳转报错:

// 新增全局变量存储冷启动拿到的通知数据
RemoteMessage? initialColdStartMessage;

// 方法签名改成Future,支持初始化时await
Future<void> FirebaseNotification.init() async {
    initNotification = InitNotification();
    listenWhenMessageComes();
    listenWhenAppInBackground();
    listenWhenAppOpned();
    listenWhenUserIsOnApp();
    // 新增:获取冷启动场景的初始通知
    await _getColdStartInitialMessage();
}

// 新增:冷启动初始消息获取方法
Future<void> _getColdStartInitialMessage() async {
  // 该方法专门获取完全终止状态下点击通知唤起App时携带的消息
  initialColdStartMessage = await FirebaseMessaging.instance.getInitialMessage();
}

// 原有方法保留,处理后台存活场景的通知点击
Future<void> listenWhenAppOpned() async {
    FirebaseMessaging.onMessageOpenedApp.listen((event) async {
      if (event.notification != null) {
        // 直接传原生返回的data Map,不要转字符串再自行解析
        await initNotification.onNotificationSelected(event.data);
      }
    });
}

2. 修复payload解析逻辑

修改init_notification.dart里的onNotificationSelected方法,直接接收Map类型参数,去掉脆弱的字符串分割逻辑,同时保证导航挂载完成后再执行跳转:

// 入参从String?改为直接接收Map<String, dynamic>类型的通知data
Future<void> onNotificationSelected(Map<String, dynamic>? payLoad) async {
    print(payLoad);
    if (payLoad != null) {
      print("result:$payLoad");
      // 等当前帧渲染完成、Navigator挂载完毕再执行跳转,避免空指针
      WidgetsBinding.instance.addPostFrameCallback((_) async {
        await goToRespectedPage(payLoad);
      });
    }
}

3. 调整main.dart启动时序,等App挂载完成后处理冷启动通知

修改main.dart的逻辑,保证通知初始化完成后再启动App,等第一帧渲染完成后检查冷启动通知数据,有则执行跳转:

void main() async {
  HttpOverrides.global = new MyHttpOverrides();
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FirebaseDynamicLink.init();
  // 等待通知初始化完成,拿到冷启动初始消息
  await FirebaseNotification.init();
  const MethodChannel('flavor')
      .invokeMethod<String>('getFlavor')
      .then((String? flavor) {
    print('STARTED WITH FLAVOR $flavor');
    if (flavor == 'PROD') {
      startPROD();
    } else if (flavor == 'UAT') {
      startUAT();
    }
  }).catchError((error) {
    print(error);
    print('FAILED TO LOAD FLAVOR');
  });
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    // 第一帧渲染完成后检查冷启动通知
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (initialColdStartMessage != null) {
        FirebaseNotification.initNotification.onNotificationSelected(initialColdStartMessage!.data);
        // 处理完清空数据,避免重复跳转
        initialColdStartMessage = null;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      navigatorKey: AuthNav.navigationKey,
      onGenerateRoute: (RouteSettings settings) {
        return makeRoute(
          context: context,
          routeName: settings.name!,
          arguments: settings.arguments,
        );
      },
    );
  }
}

4. 优化跳转逻辑增加空判断

修改goToRespectedPage方法,增加导航状态空判断,避免极端场景下的空指针报错:

Future<void> goToRespectedPage(Map<String, dynamic> result) async {
    try {
      final navigatorState = AuthNav.navigationKey.currentState;
      // 导航未挂载时直接返回,不执行跳转
      if (navigatorState == null) return;
      
      var routeName = NotificationRouteModel.fromJson(result).route;
      if (routeName.isNotEmpty) {
        if (userData != null) {
          switch (routeName) {
            case '/ViewPostPage':
              await navigatorState.pushReplacementNamed(routeName,
                  arguments: int.parse(result['kmdpWallId']));
              break;
            default:
              await navigatorState.pushNamed("/MainPage");
          }
        } else {
          await navigatorState.pushNamed("/LoginPage");
        }
      }
    } catch (err, stack) {
      print(err);
      print(stack);
    }
}

验证注意点

  • 冷启动测试时要先把应用从最近任务列表完全划掉,再点击通知栏通知拉起
  • 通知的data字段不要放嵌套结构,保证key-value都是基础类型,避免解析异常
  • Android12及以上版本要确认通知权限已经申请,否则通知可能无法正常展示

内容的提问来源于stack exchange,提问作者Developers If Else Solutions

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:30:05