Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[firebase_messaging] Android: Receiving Data messages if app terminated #1898

Closed
wants to merge 10 commits into from
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 @@
## 5.2.0

* Android: Receiving Data messages if app terminated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Android: Receiving Data messages if app terminated
* On Android, now supports receiving data messages if the app is terminated.


## 5.1.3

* Update google-services Android gradle plugin to 4.3.0 in documentation and examples.
Expand Down
31 changes: 30 additions & 1 deletion packages/firebase_messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Messages are sent to your Flutter app via the `onMessage`, `onLaunch`, and `onRe
| --------------------------: | ----------------- | ----------------- | -------------- |
| **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. |
| **Data Message on Android** | `onMessage` | `onMessage` while app stays in the background. | *not supported by plugin, message is lost* |
| **Data Message on Android** | `onMessage` | `onMessage` while app stays in the background. | see "Receiving Data messages(Android)" |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| **Data Message on Android** | `onMessage` | `onMessage` while app stays in the background. | see "Receiving Data messages(Android)" |
| **Data Message on Android** | `onMessage` | `onMessage` while app stays in the background. | see "Receiving Data messages (Android)" |

| **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. |

Additional reading: Firebase's [About FCM Messages](https://firebase.google.com/docs/cloud-messaging/concept-options).
Expand All @@ -108,6 +108,35 @@ Future<void> _handleNotification (Map<dynamic, dynamic> message, bool dialog) as
}
````

## Receiving Data messages(Android)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Receiving Data messages(Android)
## Receiving data messages (Android)


To receiving data messages, follow these steps:
1. Include the following code within the `<activity>` tag of your `android/app/src/main/AndroidManifest.xml`:
```xml
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="io.flutter.plugins.firebasemessaging.BACKGROUND_NOTIFICATION" />
</intent-filter>
</receiver>
<meta-data
android:name="flutter.firebase.data.message"
android:value="true" />
```
2. implement onReceive method. onReceive called if app terminated or app in background.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2. implement onReceive method. onReceive called if app terminated or app in background.
2. Implement the `onReceive` method. `onReceive` called if the app is terminated or in the background.

```java
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
RemoteMessage remoteMessage = intent.getParcelableExtra(EXTRA_REMOTE_MESSAGE);

Map<String, String> data = remoteMessage.getData();

String title = data.get("title");
String body = data.get("body");
}
}
```

## Sending Messages
Refer to the [Firebase documentation](https://firebase.google.com/docs/cloud-messaging/) about FCM for all the details about sending messages to your app. When sending a notification message to an Android device, you need to make sure to set the `click_action` property of the message to `FLUTTER_NOTIFICATION_CLICK`. Otherwise the plugin will be unable to deliver the notification to your app when the users clicks on it in the system tray.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private FirebaseMessagingPlugin(Registrar registrar, MethodChannel channel) {

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(FlutterFirebaseMessagingService.ACTION_TOKEN);
intentFilter.addAction(FlutterFirebaseMessagingService.ACTION_REMOTE_MESSAGE);
intentFilter.addAction(FlutterFirebaseMessagingService.ACTION_FOREGROUND_REMOTE_MESSAGE);
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(registrar.context());
manager.registerReceiver(this, intentFilter);
}
Expand All @@ -70,7 +70,7 @@ public void onReceive(Context context, Intent intent) {
if (action.equals(FlutterFirebaseMessagingService.ACTION_TOKEN)) {
String token = intent.getStringExtra(FlutterFirebaseMessagingService.EXTRA_TOKEN);
channel.invokeMethod("onToken", token);
} else if (action.equals(FlutterFirebaseMessagingService.ACTION_REMOTE_MESSAGE)) {
} else if (action.equals(FlutterFirebaseMessagingService.ACTION_FOREGROUND_REMOTE_MESSAGE)) {
RemoteMessage message =
intent.getParcelableExtra(FlutterFirebaseMessagingService.EXTRA_REMOTE_MESSAGE);
Map<String, Object> content = parseRemoteMessage(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,60 @@

public class FlutterFirebaseMessagingService extends FirebaseMessagingService {

public static final String ACTION_REMOTE_MESSAGE =
"io.flutter.plugins.firebasemessaging.NOTIFICATION";
public static final String ACTION_FOREGROUND_REMOTE_MESSAGE =
"io.flutter.plugins.firebasemessaging.FOREGROUND_NOTIFICATION";
public static final String ACTION_BACKGROUND_REMOTE_MESSAGE =
"io.flutter.plugins.firebasemessaging.BACKGROUND_NOTIFICATION";
public static final String EXTRA_REMOTE_MESSAGE = "notification";

public static final String ACTION_TOKEN = "io.flutter.plugins.firebasemessaging.TOKEN";
public static final String EXTRA_TOKEN = "token";

/**
* true, if application receive messages with type "Data messages" About FCM messages
* {@https://firebase.google.com/docs/cloud-messaging/concept-options}
*/
private boolean isDataMessages;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is private it probably doesn't need a doc comment.


@Override
public void onCreate() {
super.onCreate();
isDataMessages = FlutterFirebaseMessagingUtils.isDataMessages(this);
}

/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Intent intent = new Intent(ACTION_REMOTE_MESSAGE);
if (!isDataMessages) {
sendForegroundBroadcast(remoteMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like you're broadcasting twice here if !isDataMessages evaluates to true. Is that what you intended? Should you return?

}

boolean applicationForeground =
FlutterFirebaseMessagingUtils.isApplicationForeground(getApplicationContext());

if (applicationForeground) {
sendForegroundBroadcast(remoteMessage);
} else {
sendBackgroundBroadcast(remoteMessage);
}
}

private void sendForegroundBroadcast(RemoteMessage remoteMessage) {
Intent intent = new Intent(ACTION_FOREGROUND_REMOTE_MESSAGE);
intent.putExtra(EXTRA_REMOTE_MESSAGE, remoteMessage);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

private void sendBackgroundBroadcast(RemoteMessage remoteMessage) {
Intent intent = new Intent(ACTION_BACKGROUND_REMOTE_MESSAGE);
intent.putExtra(EXTRA_REMOTE_MESSAGE, remoteMessage);
intent.setPackage(getPackageName());
sendBroadcast(intent);
}
/**
* Called when a new token for the default Firebase project is generated.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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.

package io.flutter.plugins.firebasemessaging;

import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Process;
import java.util.List;

public class FlutterFirebaseMessagingUtils {

private static final String DATA_MESSAGES_KEY = "flutter.firebase.data.message";

static boolean isDataMessages(Context context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method name came across as a bit confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this method name kind of confusing. I think it might make sense to call it dataMessagesEnabled since you're intending this as an app-wide flag. Would it make sense to just do something by default? React Native Firebase doesn't seem to have this flag so I'm wondering how the approaches compare.

try {
ApplicationInfo info =
context
.getPackageManager()
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = info.metaData;
return bundle.getBoolean(DATA_MESSAGES_KEY);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

public static boolean isApplicationForeground(Context context) {
KeyguardManager keyguardManager =
(KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);

if (keyguardManager.inKeyguardRestrictedInputMode()) {
return false;
}
int myPid = Process.myPid();

ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

List<ActivityManager.RunningAppProcessInfo> list;

if ((list = activityManager.getRunningAppProcesses()) != null) {
for (ActivityManager.RunningAppProcessInfo aList : list) {
ActivityManager.RunningAppProcessInfo info;
if ((info = aList).pid == myPid) {
return info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,14 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<receiver android:name=".FlutterBackgroundMessagesReceiver">
<intent-filter>
<action android:name="io.flutter.plugins.firebasemessaging.BACKGROUND_NOTIFICATION" />
</intent-filter>
</receiver>
<meta-data
android:name="flutter.firebase.data.message"
android:value="true" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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.

package io.flutter.plugins.firebasemessagingexample;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.text.TextUtils;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;

public class FlutterBackgroundMessagesReceiver extends BroadcastReceiver {

private static final String DEFAULT_CHANNEL_ID = "default";
public static final String EXTRA_REMOTE_MESSAGE = "notification";

@Override
public void onReceive(Context context, Intent intent) {
showNotification(context, intent);
}

private void showNotification(Context context, Intent intent) {
RemoteMessage remoteMessage = intent.getParcelableExtra(EXTRA_REMOTE_MESSAGE);

Map<String, String> data = remoteMessage.getData();

String title = data.get("title");
String body = data.get("body");

NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

setupNotificationChannel(notificationManager);

Intent messageDataIntent = new Intent("FLUTTER_NOTIFICATION_CLICK");
messageDataIntent.setPackage(context.getPackageName());
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
messageDataIntent.putExtra(key, value);
}

PendingIntent pendingIntent = PendingIntent.getActivity(context, 123, messageDataIntent, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PendingIntent pendingIntent = PendingIntent.getActivity(context, 123, messageDataIntent, 0);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 123, messageDataIntent, 0);

The 123 here looks a little magical, perhaps we should move it out into a constant?


Resources resources = context.getResources();

int iconId = resources.getIdentifier("ic_launcher", "mipmap", context.getPackageName());
NotificationCompat.Builder builder =
new NotificationCompat.Builder(context, DEFAULT_CHANNEL_ID)
.setColor(ContextCompat.getColor(context, android.R.color.black))
.setSmallIcon(iconId)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), iconId))
.setAutoCancel(true)
.setContentIntent(pendingIntent);

if (TextUtils.isEmpty(title)) {
CharSequence appLabel = context.getApplicationInfo().loadLabel(context.getPackageManager());
builder.setContentTitle(appLabel);
} else {
builder.setContentTitle(title);
}

if (!TextUtils.isEmpty(body)) {
builder.setStyle((new NotificationCompat.BigTextStyle()).bigText(body)).setContentText(body);
}

Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
builder.setSound(uri);
notificationManager.notify(System.currentTimeMillis() + "", 0, builder.build());
}

private void setupNotificationChannel(NotificationManager notificationManager) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
if (notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID) != null) {
return;
}

NotificationChannel channel =
new NotificationChannel(
DEFAULT_CHANNEL_ID, "Primary Channel", NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(true);
channel.enableVibration(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(channel);
}
}
}
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.1.3
version: 5.2.0

flutter:
plugin:
Expand Down