Flutter - FCM message not coming when app is closed
Asked Answered
V

3

9

I have used FCM along with Flutter local notification for receiving messages. Flutter local notifiation is used to show notification when app is in foreground. Currently, I am using Android 12 (Poco X3 PRO).

When I terminate the application and send a message from Firebase Console, the below message appears in the logcat.

2022-07-26 07:40:16.751 11370-11370/? W/GCM: broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE flg=0x10000000 pkg=au.org.nrna.test.app (has extras) }

I have checked the firebase cloud messaging documentation linked HERE, however there is no mention of message handling in terminated state.

Similarly, when app is in foreground, the message appears in the notification tray only. It does not show up on the screen at all.

FILE: main.dart

import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

import 'package:device_preview/device_preview.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

import 'core/apps/app.dart';
import 'core/apps/device_preview_app.dart';
import 'core/bloc_observers/bloc_observer.dart';
import 'core/config_reader/config_reader.dart';
import 'core/injections/injections.dart';
import 'core/routes/app_router.gr.dart';

final appRouter = AppRouter();

void main() async {
  BlocOverrides.runZoned(
    () async {
      runZonedGuarded<Future<void>>(() async {
        initAndRunApp();
      },
          (error, stack) => FirebaseCrashlytics.instance
              .recordError(error, stack, fatal: true));
    },
    blocObserver: MyBlocObserver(),
  );
}

void onDidReceiveLocalNotification(
    int id, String? title, String? body, String? payload) {
  // display a dialog with the notification details, tap ok to go to another page

  debugPrint(
      'Notification[#$id] - Title: $title, Body: $body Payload: $payload');
}

void onSelectNotification(String? payload) {
  if (payload != null) {
    Map<String, dynamic> payloadMap = jsonDecode(payload);

    debugPrint(payloadMap.toString());
  }
}

Future _setupPushNotification() async {
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  const AndroidInitializationSettings androidInitializationSettings =
      AndroidInitializationSettings('launch_background');

  const IOSInitializationSettings iosInitializationSettings =
      IOSInitializationSettings(
          onDidReceiveLocalNotification: onDidReceiveLocalNotification);

  const InitializationSettings initializationSettings = InitializationSettings(
    iOS: iosInitializationSettings,
    android: androidInitializationSettings,
  );

  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onSelectNotification: onSelectNotification);

  final bool? result = await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          IOSFlutterLocalNotificationsPlugin>()
      ?.requestPermissions(
        alert: true,
        badge: true,
        sound: true,
      );

  var androidDetails = const AndroidNotificationDetails(
    'com.test.app.notification_channel',
    'Test Notification Channel',
    channelDescription: 'Notification Channel for Test App for Android',
    priority: Priority.high,
  );
  var iosDetails = const IOSNotificationDetails();
  var generalNotificationDetails =
      NotificationDetails(android: androidDetails, iOS: iosDetails);

  FirebaseMessaging.onBackgroundMessage(_onBackgroundMessageHandler);

  FirebaseMessaging.onMessage.listen((message) {
    debugPrint(
        'FCM NOTIFICATION[#${message.messageId}] - Data: ${message.data} Title: ${message.notification?.title} Body: ${message.notification?.body}');

    RemoteNotification? notification = message.notification;
    AndroidNotification? androidNotification = message.notification?.android;
    AppleNotification? appleNotification = message.notification?.apple;

    String? payloadJsonStr;
    if (message.data.isNotEmpty) {
      payloadJsonStr = jsonEncode(message.data);
    }

    if (notification != null && androidNotification != null) {
      flutterLocalNotificationsPlugin.show(notification.hashCode,
          notification.title, notification.body, generalNotificationDetails,
          payload: payloadJsonStr);
    }
  });
}

Future<void> _onBackgroundMessageHandler(RemoteMessage message) async {
  debugPrint(
      'Background FCM NOTIFICATION[#${message.messageId}] - Data: ${message.data} Title: ${message.notification?.title} Body: ${message.notification?.body}');
}

Future _initialiseApp() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // setup Push Notifications
  _setupPushNotification();

  // Pass all uncaught errors from the framework to Crashlytics.
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;

  // GetIt configuration
  configureDependencies();
  await getIt<ConfigReader>().initialize();
}

Future initAndRunApp() async {
  await _initialiseApp();

  Isolate.current.addErrorListener(RawReceivePort((pair) async {
    final List<dynamic> errorAndStacktrace = pair;
    await FirebaseCrashlytics.instance.recordError(
      errorAndStacktrace.first,
      errorAndStacktrace.last,
      fatal: true,
    );
  }).sendPort);

  final fcmToken = await FirebaseMessaging.instance.getToken();
  debugPrint(fcmToken);

  if (!kReleaseMode && getIt<ConfigReader>().isDevicePreviewEnabled) {
    runApp(
      DevicePreview(
        enabled: !kReleaseMode,
        builder: (context) => const DevicePreviewApp(), // Wrap your app
      ),
    );
  } else {
    runApp(const MyApp());
  }
}

FILE: AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.app">
    <uses-permission android:name="android.permission.INTERNET"/>
   <application
        android:label="My App"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

Vostok answered 26/7, 2022 at 2:10 Comment(1)
@Adriaan I added the solution below already, however others were adding newer answers. So, in such case, I was trying to save others time. I apologize if that is against policy and thank you for your time editing the post.Vostok
V
17

The error seems to occur when running the app in debug mode. If you stop the debugging, it is treated as force-stop or killing the application.

Firebase documentation states that notification will not show on a force-stop or killed app unless the app is opened again.

DOCUMENTATION: Receive messages in a Flutter app. Look for the section titled Receive messages in a Flutter app in the page.

Vostok answered 26/7, 2022 at 3:12 Comment(0)
S
13

try running your app with release mode using flutter run --release and check

Spool answered 26/7, 2022 at 5:28 Comment(0)
L
3

Just notice that:

  • In debug mode, when app is terminated, Android won't show, iOS shows
  • In release mode, both Android and iOS show as normal
Lodi answered 28/11, 2023 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.