Skip to content

Commit

Permalink
refactor(messaging, android): removed unsafe & deprecated API. E.g. `…
Browse files Browse the repository at this point in the history
…LocalBroadcastManager` replaced by `LiveData` (#11659)
  • Loading branch information
russellwheatley committed Oct 3, 2023
1 parent 639bc2e commit 690160f
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 71 deletions.
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

0 comments on commit 690160f

Please sign in to comment.