Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/firebase_messaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 6.0.0
* **Breaking Change** Separated onLaunch to an specific method to be able to retrieve launch
message in a synchronous way.

## 5.0.4

* Automatically use version from pubspec.yaml when reporting usage to Firebase.
Expand Down
12 changes: 9 additions & 3 deletions packages/firebase_messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,18 @@ Next, you should probably request permissions for receiving Push Notifications.

## Receiving Messages

Messages are sent to your Flutter app via the `onMessage`, `onLaunch`, and `onResume` callbacks that you configured with the plugin during setup. Here is how different message types are delivered on the supported platforms:
Launch messages (when application is closed) are retrieved on application init so you can act based on the notification data:

```dart
Map<String, dynamic> message = await _firebaseMessaging.getLaunchMessage();
```

Messages when the application is not in foreground are sent to your Flutter app via the `onMessage` and `onResume` callbacks that you configured with the plugin during setup. Here is how different message types are delivered on the supported platforms:

| | App in Foreground | App in Background | App Terminated |
| --------------------------: | ----------------- | ----------------- | -------------- |
| **Notification on Android** | `onMessage` | Notification is delivered to system tray. When the user clicks on it to open app `onResume` fires if `click_action: FLUTTER_NOTIFICATION_CLICK` is set (see below). | Notification is delivered to system tray. When the user clicks on it to open app `onLaunch` fires if `click_action: FLUTTER_NOTIFICATION_CLICK` is set (see below). |
| **Notification on iOS** | `onMessage` | Notification is delivered to system tray. When the user clicks on it to open app `onResume` fires. | Notification is delivered to system tray. When the user clicks on it to open app `onLaunch` fires. |
| **Notification on Android** | `onMessage` | Notification is delivered to system tray. When the user clicks on it to open app `onResume` fires if `click_action: FLUTTER_NOTIFICATION_CLICK` is set (see below). | Notification is delivered to system tray. When the user clicks on it to open app `geLaunchMessage` fires if `click_action: FLUTTER_NOTIFICATION_CLICK` is set (see below). |
| **Notification on iOS** | `onMessage` | Notification is delivered to system tray. When the user clicks on it to open app `onResume` fires. | Notification is delivered to system tray. When the user clicks on it to open app `getLaunchMessage` fires. |
| **Data Message on Android** | `onMessage` | `onMessage` while app stays in the background. | *not supported by plugin, message is lost* |
| **Data Message on iOS** | `onMessage` | Message is stored by FCM and delivered to app via `onMessage` when the app is brought back to foreground. | Message is stored by FCM and delivered to app via `onMessage` when the app is brought back to foreground. |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ public void onComplete(@NonNull Task<InstanceIdResult> task) {
channel.invokeMethod("onToken", task.getResult().getToken());
}
});
result.success(null);
} else if ("getLaunchMessage".equals(call.method)) {
if (registrar.activity() != null) {
sendMessageFromIntent("onLaunch", registrar.activity().getIntent());
Map<String, Object> message = this.getMessageFromIntent(registrar.activity().getIntent());
result.success(message);
return;
}
result.success(null);
} else if ("subscribeToTopic".equals(call.method)) {
Expand Down Expand Up @@ -176,15 +180,14 @@ public boolean onNewIntent(Intent intent) {
return res;
}

/** @return true if intent contained a message to send. */
private boolean sendMessageFromIntent(String method, Intent intent) {
private Map<String, Object> getMessageFromIntent(Intent intent) {
if (CLICK_ACTION_VALUE.equals(intent.getAction())
|| CLICK_ACTION_VALUE.equals(intent.getStringExtra("click_action"))) {
Map<String, Object> message = new HashMap<>();
Bundle extras = intent.getExtras();

if (extras == null) {
return false;
return null;
}

Map<String, Object> notificationMap = new HashMap<>();
Expand All @@ -199,10 +202,20 @@ private boolean sendMessageFromIntent(String method, Intent intent) {

message.put("notification", notificationMap);
message.put("data", dataMap);
return message;
}
return null;
}

channel.invokeMethod(method, message);
return true;
/** @return true if intent contained a message to send. */
private boolean sendMessageFromIntent(String method, Intent intent) {
Map<String, Object> message = this.getMessageFromIntent(intent);

if (message == null) {
return false;
}
return false;

channel.invokeMethod(method, message);
return true;
}
}
15 changes: 11 additions & 4 deletions packages/firebase_messaging/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,22 @@ class _PushMessagingExampleState extends State<PushMessagingExample> {
@override
void initState() {
super.initState();
}

void initPushNotifications() async {
final Map<String, dynamic> message =
await _firebaseMessaging.getLaunchMessage();

if (message != null) {
print("getLaunchMessage $message");
_navigateToItemDetail(message);
}

_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
_showItemDialog(message);
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
_navigateToItemDetail(message);
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
_navigateToItemDetail(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];

result(nil);
} else if ([@"getLaunchMessage" isEqualToString:method]) {
result(_launchNotification);
} else if ([@"configure" isEqualToString:method]) {
[FIRMessaging messaging].shouldEstablishDirectChannel = true;
[[UIApplication sharedApplication] registerForRemoteNotifications];
if (_launchNotification != nil) {
[_channel invokeMethod:@"onLaunch" arguments:_launchNotification];
}
result(nil);
} else if ([@"subscribeToTopic" isEqualToString:method]) {
NSString *topic = call.arguments;
Expand Down
10 changes: 5 additions & 5 deletions packages/firebase_messaging/lib/firebase_messaging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class FirebaseMessaging {
final Platform _platform;

MessageHandler _onMessage;
MessageHandler _onLaunch;
MessageHandler _onResume;

/// On iOS, prompts the user for notification permissions the first time
Expand Down Expand Up @@ -59,16 +58,19 @@ class FirebaseMessaging {
/// Sets up [MessageHandler] for incoming messages.
void configure({
MessageHandler onMessage,
MessageHandler onLaunch,
MessageHandler onResume,
}) {
_onMessage = onMessage;
_onLaunch = onLaunch;
_onResume = onResume;
_channel.setMethodCallHandler(_handleMethod);
_channel.invokeMethod<void>('configure');
}

Future<Map<dynamic, dynamic>> getLaunchMessage() async {
return await _channel
.invokeMethod<Map<dynamic, dynamic>>('getLaunchMessage');
}

final StreamController<String> _tokenStreamController =
StreamController<String>.broadcast();

Expand Down Expand Up @@ -126,8 +128,6 @@ class FirebaseMessaging {
return null;
case "onMessage":
return _onMessage(call.arguments.cast<String, dynamic>());
case "onLaunch":
return _onLaunch(call.arguments.cast<String, dynamic>());
case "onResume":
return _onResume(call.arguments.cast<String, dynamic>());
default:
Expand Down
2 changes: 1 addition & 1 deletion packages/firebase_messaging/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Cloud Messaging, a cross-platform
messaging solution that lets you reliably deliver messages on Android and iOS.
author: Flutter Team <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/firebase_messaging
version: 5.0.4
version: 6.0.0

flutter:
plugin:
Expand Down
45 changes: 36 additions & 9 deletions packages/firebase_messaging/test/firebase_messaging_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,32 +89,59 @@ void main() {
expect((await iosSettingsFromStream).toMap(), iosSettings.toMap());
});

test('getLaunchMessage', () async {
final MethodChannel channel =
const MethodChannel('plugins.flutter.io/firebase_messaging');
firebaseMessaging =
FirebaseMessaging.private(channel, const LocalPlatform());

channel.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'getLaunchMessage':
return <dynamic, dynamic>{
'notification': <dynamic, dynamic>{
'title': 'Title',
'body': 'Body',
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
},
'data': <dynamic, dynamic>{
'variable1': 'value1',
'variable2': 'value2',
},
};
default:
return null;
}
});

final Map<dynamic, dynamic> message =
await firebaseMessaging.getLaunchMessage();

expect(message['notification']['title'], 'Title');
expect(message['notification']['body'], 'Body');
expect(
message['notification']['click_action'], 'FLUTTER_NOTIFICATION_CLICK');
expect(message['data']['variable1'], 'value1');
expect(message['data']['variable2'], 'value2');
});

test('incoming messages', () async {
final Completer<dynamic> onMessage = Completer<dynamic>();
final Completer<dynamic> onLaunch = Completer<dynamic>();
final Completer<dynamic> onResume = Completer<dynamic>();

firebaseMessaging.configure(onMessage: (dynamic m) async {
onMessage.complete(m);
}, onLaunch: (dynamic m) async {
onLaunch.complete(m);
}, onResume: (dynamic m) async {
onResume.complete(m);
});
final dynamic handler =
verify(mockChannel.setMethodCallHandler(captureAny)).captured.single;

final Map<String, dynamic> onMessageMessage = <String, dynamic>{};
final Map<String, dynamic> onLaunchMessage = <String, dynamic>{};
final Map<String, dynamic> onResumeMessage = <String, dynamic>{};

await handler(MethodCall('onMessage', onMessageMessage));
expect(await onMessage.future, onMessageMessage);
expect(onLaunch.isCompleted, isFalse);
expect(onResume.isCompleted, isFalse);

await handler(MethodCall('onLaunch', onLaunchMessage));
expect(await onLaunch.future, onLaunchMessage);
expect(onResume.isCompleted, isFalse);

await handler(MethodCall('onResume', onResumeMessage));
Expand Down