Flutter FCM Push Notifications - Routing to a widget and displaying when app has foreground

178 views Asked by At

I am working on integrating FCM notifications into my Flutter app but haven't found many examples on displaying the notification details in a widget (plenty of documentation for printing notification data to the console). I pieced the below together from various tutorials, but there are two problems: No notification is received when the app has foreground, and when a background notification is received, tapping it launches the app but without navigating to the NotificationWidget as expected:

// firebase_api.dart 

Future<void> handleBackgroundMessage(RemoteMessage? message) async {
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  if (message == null) return;

  print("Message received: ${message.data}");

  navigatorKey.currentState?.pushNamed(
    NotificationWidget.route,
    arguments: message,
  );
}

class FirebaseApi {
  static final _firebaseMessaging = FirebaseMessaging.instance;

  static const _androidChannel = AndroidNotificationChannel(
    'my_app_notifications',
    'My App Notifications',
    description: 'My App Notifications',
    importance: Importance.defaultImportance,
  );

  final _localNotifications = FlutterLocalNotificationsPlugin();

  static void handleMessage(RemoteMessage? message) {
    if (message == null) return;

    print("Message received: ${message.data}");

    navigatorKey.currentState?.pushNamed(
      NotificationWidget.route,
      arguments: message,
    );
  }

  Future initPushNotifications() async {
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );

    FirebaseMessaging.instance.getInitialMessage().then(handleMessage);
    FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
    FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
    FirebaseMessaging.onMessage.listen((event) {
      final notification = event.notification;
      if (notification == null) return;

      _localNotifications.show(
        notification.hashCode,
        notification.title,
        notification.body,
        NotificationDetails(
            android: AndroidNotificationDetails(
              _androidChannel.id,
              _androidChannel.name,
              channelDescription: _androidChannel.description,
              icon: 'assets/image.png',
              priority: Priority.high,
            ),
            iOS: const DarwinNotificationDetails()),
        payload: jsonEncode(event.toMap()),
      );
    });
  }

  Future initLocalNotifications() async {
    const iOS = DarwinInitializationSettings();
    const android = AndroidInitializationSettings('assets/image.png');
    const settings = InitializationSettings(android: android, iOS: iOS);
    await _localNotifications.initialize(
      settings,
      onDidReceiveNotificationResponse: (payload) {
        final message = RemoteMessage.fromMap(jsonDecode(payload as String));
        handleMessage(message);
      },
    );

    final platform = _localNotifications.resolvePlatformSpecificImplementation<
        AndroidFlutterLocalNotificationsPlugin>();
    await platform?.createNotificationChannel(_androidChannel);
  }

  Future<void> initNotifications() async {
    await _firebaseMessaging.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );

    initPushNotifications();
    initLocalNotifications();

    print("FirebaseApi was initialized");
  }
}
//notification.dart 

class NotificationWidget extends StatelessWidget {
  const NotificationWidget({Key? key}) : super(key: key);

  static const route = '/NotificationWidget';

  @override
  Widget build(BuildContext context) {
    final RemoteMessage message = ModalRoute.of(context)!.settings.arguments as RemoteMessage;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Message From My App"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Spacer(),
            Text('${message.notification?.title}'),
            Text('${message.notification?.body}'),
            Text('${message.data}'),
            const Spacer(),
          ],
        ),
      ),
    );
  }
}
// main.dart 

final navigatorKey = GlobalKey<NavigatorState>();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  var firebaseApi = FirebaseApi();
  await firebaseApi.initNotifications();
  await firebaseApi.initLocalNotifications();

  final session = await AudioSession.instance;
  await session.configure(const AudioSessionConfiguration.music());
  audioHandler = await AudioService.init(
    builder: () => AudioPlayerHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId:
          'com.me.my_app_app.channel.audio',
      androidNotificationChannelName: 'My App',
      androidNotificationOngoing: true,
    ),
  );
  runApp(const MyApp());
}

AndroidManifest.xml

<!-- Outside of activity tags but inside application tags -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="my_app_notifications" />

<!-- Inside of activity tags -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

            <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

Notably, the same android notification channel used by both AudioService and FirebaseApi. This seems unrelated to the specified route not displaying when a user taps the navigation.

0

There are 0 answers