384 lines
13 KiB
Dart
384 lines
13 KiB
Dart
// 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';
|
|
|
|
// import 'firebase_options.dart';
|
|
// import 'message.dart';
|
|
// import 'message_list.dart';
|
|
// import 'permissions.dart';
|
|
// import 'token_monitor.dart';
|
|
|
|
/// Working example of FirebaseMessaging.
|
|
/// Please use this in order to verify messages are working in foreground, background & terminated state.
|
|
/// Setup your app following this guide:
|
|
/// https://firebase.google.com/docs/cloud-messaging/flutter/client#platform-specific_setup_and_requirements):
|
|
///
|
|
/// Once you've completed platform specific requirements, follow these instructions:
|
|
/// 1. Install melos tool by running `flutter pub global activate melos`.
|
|
/// 2. Run `melos bootstrap` in FlutterFire project.
|
|
/// 3. In your terminal, root to ./packages/firebase_messaging/firebase_messaging/example directory.
|
|
/// 4. Run `flutterfire configure` in the example/ directory to setup your app with your Firebase project.
|
|
/// 5. Open `token_monitor.dart` and change `vapidKey` to yours.
|
|
/// 6. Run the app on an actual device for iOS, android is fine to run on an emulator.
|
|
/// 7. Use the following script to send a message to your device: scripts/send-message.js. To run this script,
|
|
/// you will need nodejs installed on your computer. Then the following:
|
|
/// a. Download a service account key (JSON file) from your Firebase console, rename it to "google-services.json" and add to the example/scripts directory.
|
|
/// b. Ensure your device/emulator is running, and run the FirebaseMessaging example app using `flutter run`.
|
|
/// c. Copy the token that is printed in the console and paste it here: https://github.com/firebase/flutterfire/blob/01b4d357e1/packages/firebase_messaging/firebase_messaging/example/lib/main.dart#L32
|
|
/// c. From your terminal, root to example/scripts directory & run `npm install`.
|
|
/// d. Run `npm run send-message` in the example/scripts directory and your app will receive messages in any state; foreground, background, terminated.
|
|
/// Note: Flutter API documentation for receiving messages: https://firebase.google.com/docs/cloud-messaging/flutter/receive
|
|
/// Note: If you find your messages have stopped arriving, it is extremely likely they are being throttled by the platform. iOS in particular
|
|
/// are aggressive with their throttling policy.
|
|
///
|
|
/// To verify that your messages are being received, you ought to see a notification appearon your device/emulator via the flutter_local_notifications plugin.
|
|
/// Define a top-level named handler which background/terminated messages will
|
|
/// call. Be sure to annotate the handler with `@pragma('vm:entry-point')` above the function declaration.
|
|
@pragma('vm:entry-point')
|
|
Future<void> _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<void> 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<void> 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<StatefulWidget> createState() => _Application();
|
|
}
|
|
|
|
class _Application extends State<Application> {
|
|
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<void> 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: <String, String>{
|
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
},
|
|
body: constructFCMPayload(_token),
|
|
);
|
|
print('FCM request for device sent!');
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
}
|
|
|
|
Future<void> 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: <Widget>[
|
|
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,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|