Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(messaging, android): removed unsafe & deprecated API. E.g. LocalBroadcastManager replaced by LiveData #11659

Merged
merged 9 commits into from Oct 3, 2023
Expand Up @@ -14,6 +14,7 @@
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;

public class FlutterFirebaseMessagingBackgroundService extends JobIntentService {
Expand All @@ -29,8 +30,10 @@ public class FlutterFirebaseMessagingBackgroundService extends JobIntentService
* Schedule the message to be handled by the {@link FlutterFirebaseMessagingBackgroundService}.
*/
public static void enqueueMessageProcessing(Context context, Intent messageIntent) {
RemoteMessage message = (RemoteMessage) messageIntent.getExtras().get("notification");
RemoteMessage message =
(RemoteMessage) Objects.requireNonNull(messageIntent.getExtras()).get("notification");

assert message != null;
enqueueWork(
context,
FlutterFirebaseMessagingBackgroundService.class,
Expand Down
Expand Up @@ -8,17 +8,15 @@

import android.Manifest;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationManagerCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
import com.google.android.gms.tasks.Tasks;
Expand All @@ -41,7 +39,7 @@
import java.util.Objects;

/** FlutterFirebaseMessagingPlugin */
public class FlutterFirebaseMessagingPlugin extends BroadcastReceiver
public class FlutterFirebaseMessagingPlugin
implements FlutterFirebasePlugin,
MethodCallHandler,
NewIntentListener,
Expand All @@ -52,6 +50,12 @@ public class FlutterFirebaseMessagingPlugin extends BroadcastReceiver
private MethodChannel channel;
private Activity mainActivity;

private final LiveData<RemoteMessage> liveDataRemoteMessage =
FlutterFirebaseRemoteMessageLiveData.getInstance();
private Observer<RemoteMessage> remoteMessageObserver;
private final LiveData<String> liveDataToken = FlutterFirebaseTokenLiveData.getInstance();
private Observer<String> tokenObserver;

private RemoteMessage initialMessage;
// We store the initial notification in a separate variable
// because we cannot set the notification key in
Expand All @@ -65,13 +69,18 @@ private void initInstance(BinaryMessenger messenger) {
channel = new MethodChannel(messenger, channelName);
channel.setMethodCallHandler(this);
permissionManager = new FlutterFirebasePermissionManager();
// Register broadcast receiver
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(FlutterFirebaseMessagingUtils.ACTION_TOKEN);
intentFilter.addAction(FlutterFirebaseMessagingUtils.ACTION_REMOTE_MESSAGE);
LocalBroadcastManager manager =
LocalBroadcastManager.getInstance(ContextHolder.getApplicationContext());
manager.registerReceiver(this, intentFilter);

remoteMessageObserver =
remoteMessage -> {
Map<String, Object> content =
FlutterFirebaseMessagingUtils.remoteMessageToMap(remoteMessage);
channel.invokeMethod("Messaging#onMessage", content);
};
tokenObserver = token -> channel.invokeMethod("Messaging#onTokenRefresh", token);
// We remove these observers in the onDetachedFromEngine method. Using "observeForever()"
// allows us to use without a LifecycleOwner.
liveDataRemoteMessage.observeForever(remoteMessageObserver);
liveDataToken.observeForever(tokenObserver);

registerPlugin(channelName, this);
}
Expand All @@ -83,7 +92,8 @@ public void onAttachedToEngine(FlutterPluginBinding binding) {

@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
LocalBroadcastManager.getInstance(binding.getApplicationContext()).unregisterReceiver(this);
liveDataToken.removeObserver(tokenObserver);
liveDataRemoteMessage.removeObserver(remoteMessageObserver);
}

@Override
Expand Down Expand Up @@ -115,33 +125,6 @@ public void onDetachedFromActivity() {
this.mainActivity = null;
}

// BroadcastReceiver implementation.
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();

if (action == null) {
return;
}

if (action.equals(FlutterFirebaseMessagingUtils.ACTION_TOKEN)) {
String token = intent.getStringExtra(FlutterFirebaseMessagingUtils.EXTRA_TOKEN);
channel.invokeMethod("Messaging#onTokenRefresh", token);
} else if (action.equals(FlutterFirebaseMessagingUtils.ACTION_REMOTE_MESSAGE)) {
RemoteMessage message;
if (android.os.Build.VERSION.SDK_INT >= 33) {
message =
intent.getParcelableExtra(
FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE, RemoteMessage.class);
} else {
message = intent.getParcelableExtra(FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE);
}
if (message == null) return;
Map<String, Object> content = FlutterFirebaseMessagingUtils.remoteMessageToMap(message);
channel.invokeMethod("Messaging#onMessage", content);
}
}

private Task<Void> deleteToken() {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();

Expand Down Expand Up @@ -216,7 +199,9 @@ private Task<Void> unsubscribeFromTopic(Map<String, Object> arguments) {

return taskCompletionSource.getTask();
}

// This API will be removed in a future release. Slated to be removed by June 2024 by Firebase.
// https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessaging#send
@SuppressWarnings("deprecation")
private Task<Void> sendMessage(Map<String, Object> arguments) {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();

Expand Down Expand Up @@ -383,9 +368,8 @@ private Task<Map<String, Integer>> requestPermissions() {
permissions.put("authorizationStatus", notificationsEnabled);
taskCompletionSource.setResult(permissions);
},
(String errorDescription) -> {
taskCompletionSource.setException(new Exception(errorDescription));
});
(String errorDescription) ->
taskCompletionSource.setException(new Exception(errorDescription)));
} else {
permissions.put("authorizationStatus", 1);
taskCompletionSource.setResult(permissions);
Expand Down Expand Up @@ -413,14 +397,14 @@ private Task<Map<String, Integer>> getPermissions() {
() -> {
try {
final Map<String, Integer> permissions = new HashMap<>();
final boolean areNotificationsEnabled;
if (Build.VERSION.SDK_INT >= 33) {
final boolean areNotificationsEnabled = checkPermissions();
permissions.put("authorizationStatus", areNotificationsEnabled ? 1 : 0);
areNotificationsEnabled = checkPermissions();
} else {
final boolean areNotificationsEnabled =
areNotificationsEnabled =
NotificationManagerCompat.from(mainActivity).areNotificationsEnabled();
permissions.put("authorizationStatus", areNotificationsEnabled ? 1 : 0);
}
permissions.put("authorizationStatus", areNotificationsEnabled ? 1 : 0);
taskCompletionSource.setResult(permissions);
} catch (Exception e) {
taskCompletionSource.setException(e);
Expand All @@ -445,22 +429,28 @@ public void onMethodCall(final MethodCall call, @NonNull final Result result) {
@SuppressWarnings("unchecked")
Map<String, Object> arguments = ((Map<String, Object>) call.arguments);

long pluginCallbackHandle = 0;
long userCallbackHandle = 0;
long pluginCallbackHandle;
long userCallbackHandle;

Object arg1 = arguments.get("pluginCallbackHandle");
Object arg2 = arguments.get("userCallbackHandle");

if (arg1 instanceof Long) {
pluginCallbackHandle = (Long) arg1;
} else {
} else if (arg1 instanceof Integer) {
pluginCallbackHandle = Long.valueOf((Integer) arg1);
} else {
throw new IllegalArgumentException(
"Expected 'Long' or 'Integer' type for 'pluginCallbackHandle'.");
}

if (arg2 instanceof Long) {
userCallbackHandle = (Long) arg2;
} else {
} else if (arg2 instanceof Integer) {
userCallbackHandle = Long.valueOf((Integer) arg2);
} else {
throw new IllegalArgumentException(
"Expected 'Long' or 'Integer' type for 'userCallbackHandle'.");
}

FlutterShellArgs shellArgs = null;
Expand Down Expand Up @@ -546,8 +536,8 @@ private Map<String, Object> getExceptionDetails(@Nullable Exception exception) {
}

@Override
public boolean onNewIntent(Intent intent) {
if (intent == null || intent.getExtras() == null) {
public boolean onNewIntent(@NonNull Intent intent) {
if (intent.getExtras() == null) {
return false;
}

Expand Down
Expand Up @@ -8,7 +8,6 @@
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.firebase.messaging.RemoteMessage;
import java.util.HashMap;

Expand Down Expand Up @@ -42,9 +41,7 @@ public void onReceive(Context context, Intent intent) {
// App in Foreground
// ------------------------
if (FlutterFirebaseMessagingUtils.isApplicationForeground(context)) {
Intent onMessageIntent = new Intent(FlutterFirebaseMessagingUtils.ACTION_REMOTE_MESSAGE);
onMessageIntent.putExtra(FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE, remoteMessage);
LocalBroadcastManager.getInstance(context).sendBroadcast(onMessageIntent);
FlutterFirebaseRemoteMessageLiveData.getInstance().postRemoteMessage(remoteMessage);
return;
}

Expand Down
Expand Up @@ -4,18 +4,14 @@

package io.flutter.plugins.firebase.messaging;

import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class FlutterFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(@NonNull String token) {
Intent onMessageIntent = new Intent(FlutterFirebaseMessagingUtils.ACTION_TOKEN);
onMessageIntent.putExtra(FlutterFirebaseMessagingUtils.EXTRA_TOKEN, token);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(onMessageIntent);
FlutterFirebaseTokenLiveData.getInstance().postToken(token);
}

@Override
Expand Down
@@ -0,0 +1,23 @@
// Copyright 2023 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.firebase.messaging;

import androidx.lifecycle.LiveData;
import com.google.firebase.messaging.RemoteMessage;

public class FlutterFirebaseRemoteMessageLiveData extends LiveData<RemoteMessage> {
private static FlutterFirebaseRemoteMessageLiveData instance;

public static FlutterFirebaseRemoteMessageLiveData getInstance() {
if (instance == null) {
instance = new FlutterFirebaseRemoteMessageLiveData();
}
return instance;
}

public void postRemoteMessage(RemoteMessage remoteMessage) {
postValue(remoteMessage);
}
}
@@ -0,0 +1,22 @@
// Copyright 2023 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.firebase.messaging;

import androidx.lifecycle.LiveData;

public class FlutterFirebaseTokenLiveData extends LiveData<String> {
private static FlutterFirebaseTokenLiveData instance;

public static FlutterFirebaseTokenLiveData getInstance() {
if (instance == null) {
instance = new FlutterFirebaseTokenLiveData();
}
return instance;
}

public void postToken(String token) {
postValue(token);
}
}
Expand Up @@ -26,6 +26,8 @@
import java.util.ArrayList;
import java.util.HashMap;

// Issue added for this file, we will migrate this in the future
@SuppressWarnings("all")
abstract class JobIntentService extends Service {
static final String TAG = "JobIntentService";

Expand Down
Expand Up @@ -26,7 +26,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
namespace 'io.flutter.plugins.firebase.messaging.example'
compileSdkVersion 33
compileSdk 33

lintOptions {
disable 'InvalidPackage'
Expand Down
@@ -1,20 +1,29 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath 'com.google.gms:google-services:4.3.15'
classpath 'com.android.tools.build:gradle:8.1.0'
classpath 'com.google.gms:google-services:4.4.0'
}
}

allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
// Use this to lint the project
// gradle.projectsEvaluated {
// tasks.withType(JavaCompile).tap {
// configureEach {
// options.compilerArgs << "-Xlint:deprecation"
// options.compilerArgs << "-Xlint:unchecked"
// }
// }
// }
}

rootProject.buildDir = '../build'
Expand Down
@@ -1,6 +1,5 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.nonFinalResIds=false
Expand Up @@ -203,6 +203,9 @@ class FirebaseMessaging extends FirebasePluginPlatform {
}

/// Send a new [RemoteMessage] to the FCM server. Android only.
/// Firebase will decommission in June 2024: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessaging#send
@Deprecated(
'This will be removed in a future release. Firebase will decommission in June 2024')
Future<void> sendMessage({
String? to,
Map<String, String>? data,
Expand Down