-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
method_channel_messaging.dart
431 lines (378 loc) 路 12.9 KB
/
method_channel_messaging.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
// ignore_for_file: require_trailing_commas
// Copyright 2020, the Chromium project authors. Please see the AUTHORS file
// for details. 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:io' show Platform;
import 'dart:ui';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../utils.dart';
import 'utils/exception.dart';
// This is the entrypoint for the background isolate. Since we can only enter
// an isolate once, we setup a MethodChannel to listen for method invocations
// from the native portion of the plugin. This allows for the plugin to perform
// any necessary processing in Dart (e.g., populating a custom object) before
// invoking the provided callback.
@pragma('vm:entry-point')
void _firebaseMessagingCallbackDispatcher() {
// Initialize state necessary for MethodChannels.
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel _channel = MethodChannel(
'plugins.flutter.io/firebase_messaging_background',
);
// This is where we handle background events from the native portion of the plugin.
_channel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'MessagingBackground#onMessage') {
final CallbackHandle handle =
CallbackHandle.fromRawHandle(call.arguments['userCallbackHandle']);
// PluginUtilities.getCallbackFromHandle performs a lookup based on the
// callback handle and returns a tear-off of the original callback.
final closure = PluginUtilities.getCallbackFromHandle(handle)!
as Future<void> Function(RemoteMessage);
try {
Map<String, dynamic> messageMap =
Map<String, dynamic>.from(call.arguments['message']);
final RemoteMessage remoteMessage = RemoteMessage.fromMap(messageMap);
await closure(remoteMessage);
} catch (e) {
// ignore: avoid_print
print(
'FlutterFire Messaging: An error occurred in your background messaging handler:');
// ignore: avoid_print
print(e);
}
} else {
throw UnimplementedError('${call.method} has not been implemented');
}
});
// Once we've finished initializing, let the native portion of the plugin
// know that it can start scheduling alarms.
_channel.invokeMethod<void>('MessagingBackground#initialized');
}
/// The entry point for accessing a Messaging.
///
/// You can get an instance by calling [FirebaseMessaging.instance].
class MethodChannelFirebaseMessaging extends FirebaseMessagingPlatform {
/// Create an instance of [MethodChannelFirebaseMessaging] with optional [FirebaseApp]
MethodChannelFirebaseMessaging({required FirebaseApp app})
: super(appInstance: app) {
if (_initialized) return;
channel.setMethodCallHandler((MethodCall call) async {
switch (call.method) {
case 'Messaging#onTokenRefresh':
_tokenStreamController.add(call.arguments as String);
break;
case 'Messaging#onMessage':
Map<String, dynamic> messageMap =
Map<String, dynamic>.from(call.arguments);
FirebaseMessagingPlatform.onMessage
.add(RemoteMessage.fromMap(messageMap));
break;
case 'Messaging#onMessageOpenedApp':
Map<String, dynamic> messageMap =
Map<String, dynamic>.from(call.arguments);
FirebaseMessagingPlatform.onMessageOpenedApp
.add(RemoteMessage.fromMap(messageMap));
break;
case 'Messaging#onBackgroundMessage':
// Apple only. Android calls via separate background channel.
Map<String, dynamic> messageMap =
Map<String, dynamic>.from(call.arguments);
return FirebaseMessagingPlatform.onBackgroundMessage
?.call(RemoteMessage.fromMap(messageMap));
default:
throw UnimplementedError('${call.method} has not been implemented');
}
});
_initialized = true;
}
late bool _autoInitEnabled;
static bool _initialized = false;
static bool _bgHandlerInitialized = false;
/// Returns a stub instance to allow the platform interface to access
/// the class instance statically.
static MethodChannelFirebaseMessaging get instance {
return MethodChannelFirebaseMessaging._();
}
/// Internal stub class initializer.
///
/// When the user code calls an auth method, the real instance is
/// then initialized via the [delegateFor] method.
MethodChannelFirebaseMessaging._() : super(appInstance: null);
/// The [MethodChannel] to which calls will be delegated.
@visibleForTesting
static const MethodChannel channel = MethodChannel(
'plugins.flutter.io/firebase_messaging',
);
final StreamController<String> _tokenStreamController =
StreamController<String>.broadcast();
// Created this to check APNS token is available before certain Apple Firebase
// Messaging requests. See this issue:
// https://github.com/firebase/flutterfire/issues/10625
Future<void> _APNSTokenCheck() async {
if (Platform.isMacOS || Platform.isIOS) {
String? token = await getAPNSToken();
if (token == null) {
throw FirebaseException(
plugin: 'firebase_messaging',
code: 'apns-token-not-set',
message:
'APNS token has not been set yet. Please ensure the APNS token is available by calling `getAPNSToken()`.',
);
}
}
}
@override
FirebaseMessagingPlatform delegateFor({required FirebaseApp app}) {
return MethodChannelFirebaseMessaging(app: app);
}
@override
FirebaseMessagingPlatform setInitialValues({bool? isAutoInitEnabled}) {
_autoInitEnabled = isAutoInitEnabled ?? false;
return this;
}
@override
bool get isAutoInitEnabled {
return _autoInitEnabled;
}
/// Returns "true" as this API is used to inform users of web browser support
@override
Future<bool> isSupported() {
return Future.value(true);
}
@override
Future<RemoteMessage?> getInitialMessage() async {
try {
Map<String, dynamic>? remoteMessageMap = await channel
.invokeMapMethod<String, dynamic>('Messaging#getInitialMessage', {
'appName': app.name,
});
if (remoteMessageMap == null) {
return null;
}
return RemoteMessage.fromMap(remoteMessageMap);
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<void> registerBackgroundMessageHandler(
BackgroundMessageHandler handler) async {
if (defaultTargetPlatform != TargetPlatform.android) {
return;
}
if (!_bgHandlerInitialized) {
_bgHandlerInitialized = true;
final CallbackHandle bgHandle = PluginUtilities.getCallbackHandle(
_firebaseMessagingCallbackDispatcher)!;
final CallbackHandle userHandle =
PluginUtilities.getCallbackHandle(handler)!;
await channel.invokeMapMethod('Messaging#startBackgroundIsolate', {
'pluginCallbackHandle': bgHandle.toRawHandle(),
'userCallbackHandle': userHandle.toRawHandle(),
});
}
}
@override
Future<void> deleteToken() async {
await _APNSTokenCheck();
try {
await channel
.invokeMapMethod('Messaging#deleteToken', {'appName': app.name});
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<String?> getAPNSToken() async {
if (defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS) {
return null;
}
try {
Map<String, String?>? data = await channel
.invokeMapMethod<String, String?>('Messaging#getAPNSToken', {
'appName': app.name,
});
return data?['token'];
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<String?> getToken({
String? vapidKey, // not used yet; web only property
}) async {
await _APNSTokenCheck();
try {
Map<String, String?>? data =
await channel.invokeMapMethod<String, String>('Messaging#getToken', {
'appName': app.name,
});
return data?['token'];
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<NotificationSettings> getNotificationSettings() async {
if (defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS &&
defaultTargetPlatform != TargetPlatform.android) {
return defaultNotificationSettings;
}
try {
Map<String, int>? response = await channel
.invokeMapMethod<String, int>('Messaging#getNotificationSettings', {
'appName': app.name,
});
return convertToNotificationSettings(response!);
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<NotificationSettings> requestPermission({
bool alert = true,
bool announcement = false,
bool badge = true,
bool carPlay = false,
bool criticalAlert = false,
bool provisional = false,
bool sound = true,
}) async {
if (defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS &&
defaultTargetPlatform != TargetPlatform.android) {
return defaultNotificationSettings;
}
try {
Map<String, int>? response = await channel
.invokeMapMethod<String, int>('Messaging#requestPermission', {
'appName': app.name,
'permissions': <String, bool>{
'alert': alert,
'announcement': announcement,
'badge': badge,
'carPlay': carPlay,
'criticalAlert': criticalAlert,
'provisional': provisional,
'sound': sound,
}
});
return convertToNotificationSettings(response!);
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<void> setAutoInitEnabled(bool enabled) async {
try {
Map<String, dynamic>? data = await channel
.invokeMapMethod<String, dynamic>('Messaging#setAutoInitEnabled', {
'appName': app.name,
'enabled': enabled,
});
_autoInitEnabled = data?['isAutoInitEnabled'] as bool;
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Stream<String> get onTokenRefresh {
return _tokenStreamController.stream;
}
@override
Future<void> setForegroundNotificationPresentationOptions({
required bool alert,
required bool badge,
required bool sound,
}) async {
if (defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.macOS) {
return;
}
try {
await channel.invokeMapMethod(
'Messaging#setForegroundNotificationPresentationOptions', {
'appName': app.name,
'alert': alert,
'badge': badge,
'sound': sound,
});
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<void> sendMessage({
required String to,
Map<String, String>? data,
String? collapseKey,
String? messageId,
String? messageType,
int? ttl,
}) async {
if (defaultTargetPlatform != TargetPlatform.android) {
throw UnimplementedError(
'Sending of messages from the Firebase Messaging SDK is only supported on Android devices.');
}
try {
await channel.invokeMapMethod('Messaging#sendMessage', {
'appName': app.name,
'to': to,
'data': data,
'collapseKey': collapseKey,
'messageId': messageId,
'messageType': messageType,
'ttl': ttl,
});
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<void> subscribeToTopic(String topic) async {
await _APNSTokenCheck();
try {
await channel.invokeMapMethod('Messaging#subscribeToTopic', {
'appName': app.name,
'topic': topic,
});
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<void> unsubscribeFromTopic(String topic) async {
await _APNSTokenCheck();
try {
await channel.invokeMapMethod('Messaging#unsubscribeFromTopic', {
'appName': app.name,
'topic': topic,
});
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
@override
Future<void> setDeliveryMetricsExportToBigQuery(bool enabled) async {
// The method is not available on iOS.
if (defaultTargetPlatform != TargetPlatform.android) {
return;
}
try {
await channel
.invokeMapMethod('Messaging#setDeliveryMetricsExportToBigQuery', {
'appName': app.name,
'enabled': enabled,
});
} catch (e, stack) {
convertPlatformException(e, stack);
}
}
}