// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:convert'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:http/http.dart' as http; import 'package:hub/backend/notification/message.dart'; import 'package:hub/backend/notification/message_list.dart'; import 'package:hub/backend/notification/permissions.dart'; import 'package:hub/backend/notification/token_monitor.dart'; import 'package:hub/firebase_options.dart'; @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await setupFlutterNotifications(); showFlutterNotification(message); // If you're going to use other Firebase services in the background, such as Firestore, // make sure you call `initializeApp` before using other Firebase services. print('Handling a background message ${message.messageId}'); } /// Create a [AndroidNotificationChannel] for heads up notifications late AndroidNotificationChannel channel; bool isFlutterLocalNotificationsInitialized = false; Future setupFlutterNotifications() async { if (isFlutterLocalNotificationsInitialized) { return; } channel = const AndroidNotificationChannel( 'high_importance_channel', // id 'High Importance Notifications', // title description: 'This channel is used for important notifications.', // description importance: Importance.high, ); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); /// Create an Android Notification Channel. /// /// We use this channel in the `AndroidManifest.xml` file to override the /// default FCM channel to enable heads up notifications. await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); /// Update the iOS foreground notification presentation options to allow /// heads up notifications. await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); isFlutterLocalNotificationsInitialized = true; } void showFlutterNotification(RemoteMessage message) { RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android; if (notification != null && android != null && !kIsWeb) { flutterLocalNotificationsPlugin.show( notification.hashCode, notification.title, notification.body, NotificationDetails( android: AndroidNotificationDetails( channel.id, channel.name, channelDescription: channel.description, // TODO add a proper drawable resource to android, for now using // one that already exists in example app. icon: 'launch_background', ), ), ); } } /// Initialize the [FlutterLocalNotificationsPlugin] package. late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); // Set the background messaging handler early on, as a named top-level function FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); if (!kIsWeb) { await setupFlutterNotifications(); } runApp(MessagingExampleApp()); } /// Entry point for the example application. class MessagingExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Messaging Example App', theme: ThemeData.dark(), routes: { '/': (context) => Application(), '/message': (context) => MessageView(), }, ); } } // Crude counter to make messages unique int _messageCount = 0; /// The API endpoint here accepts a raw FCM payload for demonstration purposes. String constructFCMPayload(String? token) { _messageCount++; return jsonEncode({ 'token': token, 'data': { 'via': 'FlutterFire Cloud Messaging!!!', 'count': _messageCount.toString(), }, 'notification': { 'title': 'Hello FlutterFire!', 'body': 'This notification (#$_messageCount) was created via FCM!', }, }); } /// Renders the example application. class Application extends StatefulWidget { @override State createState() => _Application(); } class _Application extends State { String? _token; String? initialMessage; bool _resolved = false; @override void initState() { super.initState(); FirebaseMessaging.instance.getInitialMessage().then( (value) => setState( () { _resolved = true; initialMessage = value?.data.toString(); }, ), ); FirebaseMessaging.onMessage.listen(showFlutterNotification); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { print('A new onMessageOpenedApp event was published!'); Navigator.pushNamed( context, '/message', arguments: MessageArguments(message, true), ); }); } Future sendPushMessage() async { if (_token == null) { print('Unable to send FCM message, no token exists.'); return; } try { await http.post( Uri.parse('https://api.rnfirebase.io/messaging/send'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: constructFCMPayload(_token), ); print('FCM request for device sent!'); } catch (e) { print(e); } } Future onActionSelected(String value) async { switch (value) { case 'subscribe': { print( 'FlutterFire Messaging Example: Subscribing to topic "fcm_test".', ); await FirebaseMessaging.instance.subscribeToTopic('fcm_test'); print( 'FlutterFire Messaging Example: Subscribing to topic "fcm_test" successful.', ); } break; case 'unsubscribe': { print( 'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test".', ); await FirebaseMessaging.instance.unsubscribeFromTopic('fcm_test'); print( 'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test" successful.', ); } break; case 'get_apns_token': { if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { print('FlutterFire Messaging Example: Getting APNs token...'); String? token = await FirebaseMessaging.instance.getAPNSToken(); print('FlutterFire Messaging Example: Got APNs token: $token'); } else { print( 'FlutterFire Messaging Example: Getting an APNs token is only supported on iOS and macOS platforms.', ); } } break; default: break; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Cloud Messaging'), actions: [ PopupMenuButton( onSelected: onActionSelected, itemBuilder: (BuildContext context) { return [ const PopupMenuItem( value: 'subscribe', child: Text('Subscribe to topic'), ), const PopupMenuItem( value: 'unsubscribe', child: Text('Unsubscribe to topic'), ), const PopupMenuItem( value: 'get_apns_token', child: Text('Get APNs token (Apple only)'), ), ]; }, ), ], ), floatingActionButton: Builder( builder: (context) => FloatingActionButton( onPressed: sendPushMessage, backgroundColor: Colors.white, child: const Icon(Icons.send), ), ), body: SingleChildScrollView( child: Column( children: [ MetaCard('Permissions', Permissions()), MetaCard( 'Initial Message', Column( children: [ Text(_resolved ? 'Resolved' : 'Resolving'), Text(initialMessage ?? 'None'), ], ), ), MetaCard( 'FCM Token', TokenMonitor((token) { _token = token; return token == null ? const CircularProgressIndicator() : SelectableText( token, style: const TextStyle(fontSize: 12), ); }), ), ElevatedButton( onPressed: () { FirebaseMessaging.instance .getInitialMessage() .then((RemoteMessage? message) { if (message != null) { Navigator.pushNamed( context, '/message', arguments: MessageArguments(message, true), ); } }); }, child: const Text('getInitialMessage()'), ), MetaCard('Message Stream', MessageList()), ], ), ), ); } } /// UI Widget for displaying metadata. class MetaCard extends StatelessWidget { final String _title; final Widget _children; // ignore: public_member_api_docs MetaCard(this._title, this._children); @override Widget build(BuildContext context) { return Container( width: double.infinity, margin: const EdgeInsets.only(left: 8, right: 8, top: 8), child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Container( margin: const EdgeInsets.only(bottom: 16), child: Text(_title, style: const TextStyle(fontSize: 18)), ), _children, ], ), ), ), ); } }