From 0834c1461dfbec2e40dbc7f102341518ea6e0f16 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Mon, 26 Aug 2019 09:32:24 -0700 Subject: [PATCH 1/8] Migration of flutter/plugins#1900 --- packages/firebase_messaging/README.md | 72 +++- .../FirebaseMessagingPlugin.java | 41 ++- .../FlutterFirebaseMessagingService.java | 311 +++++++++++++++++- .../lib/firebase_messaging.dart | 57 ++++ .../test/firebase_messaging_test.dart | 8 + 5 files changed, 483 insertions(+), 6 deletions(-) diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index 1d71ab81d848..fa6558c34024 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -54,7 +54,77 @@ Note: When you are debugging on Android, use a device or AVD with Google Play se ``` - +#### Optionally handle background messages + +By default background messaging is not enabled. To handle messages in the background: + +1. Add an Application.java class to your app + + ``` + package io.flutter.plugins.firebasemessagingexample; + + import io.flutter.app.FlutterApplication; + import io.flutter.plugin.common.PluginRegistry; + import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; + import io.flutter.plugins.GeneratedPluginRegistrant; + import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService; + + public class Application extends FlutterApplication implements PluginRegistrantCallback { + @Override + public void onCreate() { + super.onCreate(); + FlutterFirebaseMessagingService.setPluginRegistrant(this); + } + + @Override + public void registerWith(PluginRegistry registry) { + GeneratedPluginRegistrant.registerWith(registry); + } + } + ``` +1. Set name property of application in `AndroidManifest.xml` + ``` + + ``` +1. Define a top level Dart method to handle background messages + ``` + Future myBackgroundMessageHandler(Map message) { + if (message.containsKey('data')) { + // Handle data message + dynamic data = message['data']; + } + + if (message.containsKey('notification')) { + // Handle notification message + dynamic notification = message['notification']; + } + + // Or do other work. + } + ``` + Note: the protocol of `data` and `notification` are in line with the + fields defined by a [RemoteMessage](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/RemoteMessage). +1. Set `onBackgroundMessage` handler when calling `configure` + ``` + _firebaseMessaging.configure( + onMessage: (Map message) async { + print("onMessage: $message"); + _showItemDialog(message); + }, + onBackgroundMessage: myBackgroundMessageHandler, + onLaunch: (Map message) async { + print("onLaunch: $message"); + _navigateToItemDetail(message); + }, + onResume: (Map message) async { + print("onResume: $message"); + _navigateToItemDetail(message); + }, + ); + ``` + Note: `configure` should be called early in the lifecycle of your application + so that it can be ready to receive messages as early as possible. See the + example app for a demonstration. ### iOS Integration diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java index 4ab56e01d299..28e7eb2f1152 100644 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java +++ b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java @@ -41,9 +41,15 @@ public class FirebaseMessagingPlugin extends BroadcastReceiver public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/firebase_messaging"); + final MethodChannel backgroundCallbackChannel = + new MethodChannel( + registrar.messenger(), "plugins.flutter.io/firebase_messaging_background"); final FirebaseMessagingPlugin plugin = new FirebaseMessagingPlugin(registrar, channel); registrar.addNewIntentListener(plugin); channel.setMethodCallHandler(plugin); + backgroundCallbackChannel.setMethodCallHandler(plugin); + + FlutterFirebaseMessagingService.setBackgroundChannel(backgroundCallbackChannel); } private FirebaseMessagingPlugin(Registrar registrar, MethodChannel channel) { @@ -99,7 +105,40 @@ private Map parseRemoteMessage(RemoteMessage message) { @Override public void onMethodCall(final MethodCall call, final Result result) { - if ("configure".equals(call.method)) { + /* Even when the app is not active the `FirebaseMessagingService` extended by + * `FlutterFirebaseMessagingService` allows incoming FCM messages to be handled. + * + * `FcmDartService#start` and `FcmDartService#initialized` are the two methods used + * to optionally setup handling messages received while the app is not active. + * + * `FcmDartService#start` sets up the plumbing that allows messages received while + * the app is not active to be handled by a background isolate. + * + * `FcmDartService#initialized` is called by the Dart side when the plumbing for + * background message handling is complete. + */ + if ("FcmDartService#start".equals(call.method)) { + long setupCallbackHandle = 0; + long backgroundMessageHandle = 0; + try { + Map callbacks = ((Map) call.arguments); + setupCallbackHandle = callbacks.get("setupHandle"); + backgroundMessageHandle = callbacks.get("backgroundHandle"); + } catch (Exception e) { + Log.e(TAG, "There was an exception when getting callback handle from Dart side"); + e.printStackTrace(); + } + FlutterFirebaseMessagingService.setBackgroundSetupHandle( + this.registrar.context(), setupCallbackHandle); + FlutterFirebaseMessagingService.startBackgroundIsolate( + this.registrar.context(), setupCallbackHandle); + FlutterFirebaseMessagingService.setBackgroundMessageHandle( + this.registrar.context(), backgroundMessageHandle); + result.success(true); + } else if ("FcmDartService#initialized".equals(call.method)) { + FlutterFirebaseMessagingService.onInitialized(); + result.success(true); + } else if ("configure".equals(call.method)) { FirebaseInstanceId.getInstance() .getInstanceId() .addOnCompleteListener( diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java index 345c0161e192..97d898dcc8f2 100644 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java +++ b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java @@ -4,10 +4,31 @@ package io.flutter.plugins.firebasemessaging; +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Process; +import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.view.FlutterCallbackInformation; +import io.flutter.view.FlutterMain; +import io.flutter.view.FlutterNativeView; +import io.flutter.view.FlutterRunArguments; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; public class FlutterFirebaseMessagingService extends FirebaseMessagingService { @@ -18,16 +39,81 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { public static final String ACTION_TOKEN = "io.flutter.plugins.firebasemessaging.TOKEN"; public static final String EXTRA_TOKEN = "token"; + private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_fcm_plugin"; + private static final String BACKGROUND_SETUP_CALLBACK_HANDLE_KEY = "background_setup_callback"; + private static final String BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY = + "background_message_callback"; + + // TODO(kroikie): make sIsIsolateRunning per-instance, not static. + private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false); + + /** Background Dart execution context. */ + private static FlutterNativeView sBackgroundFlutterView; + + private static MethodChannel sBackgroundChannel; + + private static Long sBackgroundMessageHandle; + + private static List sBackgroundMessageQueue = + Collections.synchronizedList(new LinkedList()); + + private static PluginRegistry.PluginRegistrantCallback sPluginRegistrantCallback; + + private static final String TAG = "FlutterFcmService"; + + private static Context sBackgroundContext; + + @Override + public void onCreate() { + super.onCreate(); + + sBackgroundContext = getApplicationContext(); + FlutterMain.ensureInitializationComplete(sBackgroundContext, null); + + // If background isolate is not running start it. + if (!sIsIsolateRunning.get()) { + SharedPreferences p = sBackgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); + long callbackHandle = p.getLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, 0); + startBackgroundIsolate(sBackgroundContext, callbackHandle); + } + } + /** * 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); - intent.putExtra(EXTRA_REMOTE_MESSAGE, remoteMessage); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + public void onMessageReceived(final RemoteMessage remoteMessage) { + // If application is running in the foreground use local broadcast to handle message. + // Otherwise use the background isolate to handle message. + if (isApplicationForeground(this)) { + Intent intent = new Intent(ACTION_REMOTE_MESSAGE); + intent.putExtra(EXTRA_REMOTE_MESSAGE, remoteMessage); + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + } else { + // If background isolate is not running yet, put message in queue and it will be handled + // when the isolate starts. + if (!sIsIsolateRunning.get()) { + sBackgroundMessageQueue.add(remoteMessage); + } else { + final CountDownLatch latch = new CountDownLatch(1); + new Handler(getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + executeDartCallbackInBackgroundIsolate( + FlutterFirebaseMessagingService.this, remoteMessage, latch); + } + }); + try { + latch.await(); + } catch (InterruptedException ex) { + Log.i(TAG, "Exception waiting to execute Dart callback", ex); + } + } + } } /** @@ -42,4 +128,221 @@ public void onNewToken(String token) { intent.putExtra(EXTRA_TOKEN, token); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } + + /** + * Setup the background isolate that would allow background messages to be handled on the Dart + * side. Called either by the plugin when the app is starting up or when the app receives a + * message while it is inactive. + * + * @param context Registrar or FirebaseMessagingService context. + * @param callbackHandle Handle used to retrieve the Dart function that sets up background + * handling on the dart side. + */ + public static void startBackgroundIsolate(Context context, long callbackHandle) { + FlutterMain.ensureInitializationComplete(context, null); + String appBundlePath = FlutterMain.findAppBundlePath(context); + FlutterCallbackInformation flutterCallback = + FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + if (flutterCallback == null) { + Log.e(TAG, "Fatal: failed to find callback"); + return; + } + + // Note that we're passing `true` as the second argument to our + // FlutterNativeView constructor. This specifies the FlutterNativeView + // as a background view and does not create a drawing surface. + sBackgroundFlutterView = new FlutterNativeView(context, true); + if (appBundlePath != null && !sIsIsolateRunning.get()) { + if (sPluginRegistrantCallback == null) { + throw new RuntimeException("PluginRegistrantCallback is not set."); + } + FlutterRunArguments args = new FlutterRunArguments(); + args.bundlePath = appBundlePath; + args.entrypoint = flutterCallback.callbackName; + args.libraryPath = flutterCallback.callbackLibraryPath; + sBackgroundFlutterView.runFromBundle(args); + sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); + } + } + + /** + * Acknowledge that background message handling on the Dart side is ready. This is called by the + * Dart side once all background initialization is complete via `FcmDartService#initialized`. + */ + public static void onInitialized() { + sIsIsolateRunning.set(true); + synchronized (sBackgroundMessageQueue) { + // Handle all the messages received before the Dart isolate was + // initialized, then clear the queue. + Iterator i = sBackgroundMessageQueue.iterator(); + while (i.hasNext()) { + executeDartCallbackInBackgroundIsolate(sBackgroundContext, i.next(), null); + } + sBackgroundMessageQueue.clear(); + } + } + + /** + * Set the method channel that is used for handling background messages. This method is only + * called when the plugin registers. + * + * @param channel Background method channel. + */ + public static void setBackgroundChannel(MethodChannel channel) { + sBackgroundChannel = channel; + } + + /** + * Set the background message handle for future use. When background messages need to be handled + * on the Dart side the handler must be retrieved in the background isolate to allow processing of + * the incoming message. This method is called by the Dart side via `FcmDartService#start`. + * + * @param context Registrar context. + * @param handle Handle representing the Dart side method that will handle background messages. + */ + public static void setBackgroundMessageHandle(Context context, Long handle) { + sBackgroundMessageHandle = handle; + + // Store background message handle in shared preferences so it can be retrieved + // by other application instances. + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); + prefs.edit().putLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, handle).apply(); + } + + /** + * Set the background message setup handle for future use. The Dart side of this plugin has a + * method that sets up the background method channel. When ready to setup the background channel + * the Dart side needs to be able to retrieve the setup method. This method is called by the Dart + * side via `FcmDartService#start`. + * + * @param context Registrar context. + * @param setupBackgroundHandle Handle representing the dart side method that will setup the + * background method channel. + */ + public static void setBackgroundSetupHandle(Context context, long setupBackgroundHandle) { + // Store background setup handle in shared preferences so it can be retrieved + // by other application instances. + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); + prefs.edit().putLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, setupBackgroundHandle).apply(); + } + + /** + * Retrieve the background message handle. When a background message is received and must be + * processed on the dart side the handle representing the Dart side handle is retrieved so the + * appropriate method can be called to process the message on the Dart side. This method is called + * by FlutterFirebaseMessagingServcie either when a new background message is received or if + * background messages were queued up while background message handling was being setup. + * + * @param context Application context. + * @return Dart side background message handle. + */ + public static Long getBackgroundMessageHandle(Context context) { + return context + .getSharedPreferences(SHARED_PREFERENCES_KEY, 0) + .getLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, 0); + } + + /** + * Process the incoming message in the background isolate. This method is called only after + * background method channel is setup, it is called by FlutterFirebaseMessagingServcie either when + * a new background message is received or after background method channel setup for queued + * messages received during setup. + * + * @param context Application or FirebaseMessagingService context. + * @param remoteMessage Message received from Firebase Cloud Messaging. + * @param latch If set will count down when the Dart side message processing is complete. Allowing + * any waiting threads to continue. + */ + private static void executeDartCallbackInBackgroundIsolate( + Context context, RemoteMessage remoteMessage, final CountDownLatch latch) { + if (sBackgroundChannel == null) { + throw new RuntimeException( + "setBackgroundChannel was not called before messages came in, exiting."); + } + + // If another thread is waiting, then wake that thread when the callback returns a result. + MethodChannel.Result result = null; + if (latch != null) { + result = + new MethodChannel.Result() { + @Override + public void success(Object result) { + latch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + latch.countDown(); + } + + @Override + public void notImplemented() { + latch.countDown(); + } + }; + } + + Map args = new HashMap<>(); + Map messageData = new HashMap<>(); + if (sBackgroundMessageHandle == null) { + sBackgroundMessageHandle = getBackgroundMessageHandle(context); + } + args.put("handle", sBackgroundMessageHandle); + + if (remoteMessage.getData() != null) { + messageData.put("data", remoteMessage.getData()); + } + if (remoteMessage.getNotification() != null) { + messageData.put("notification", remoteMessage.getNotification()); + } + + args.put("message", messageData); + + sBackgroundChannel.invokeMethod("handleBackgroundMessage", args, result); + } + + /** + * Set the registrant callback. This is called by the app's Application class if background + * message handling is enabled. + * + * @param callback Application class which implements PluginRegistrantCallback. + */ + public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) { + sPluginRegistrantCallback = callback; + } + + /** + * Identify if the application is currently in a state where user interaction is possible. This + * method is only called by FlutterFirebaseMessagingService when a message is received to + * determine how the incoming message should be handled. + * + * @param context FlutterFirebaseMessagingService context. + * @return True if the application is currently in a state where user interaction is possible, + * false otherwise. + */ + // TODO(kroikie): Find a better way to determine application state. + private 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 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; + } } diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index f40420c6dbe5..794aa9980a70 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -3,13 +3,54 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:platform/platform.dart'; typedef Future MessageHandler(Map message); +/// Setup method channel to handle Firebase Cloud Messages received while +/// the Flutter app is not active. The handle for this method is generated +/// and passed to the Android side so that the background isolate knows where +/// to send background messages for processing. +/// +/// Your app should never call this method directly, this is only for use +/// by the firebase_messaging plugin to setup background message handling. +@visibleForTesting +Future fcmSetupBackgroundChannel( + {MethodChannel backgroundChannel = const MethodChannel( + 'plugins.flutter.io/firebase_messaging_background')}) async { + // Setup Flutter state needed for MethodChannels. + WidgetsFlutterBinding.ensureInitialized(); + + // This is where the magic happens and we handle background events from the + // native portion of the plugin. + backgroundChannel.setMethodCallHandler((MethodCall call) async { + if (call.method == 'handleBackgroundMessage') { + final CallbackHandle handle = + CallbackHandle.fromRawHandle(call.arguments['handle']); + final Function handlerFunction = + PluginUtilities.getCallbackFromHandle(handle); + try { + await handlerFunction( + Map.from(call.arguments['message'])); + } catch (e) { + print('Unable to handle incoming background message.'); + print(e); + } + return Future.value(); + } + }); + +// Once we've finished initializing, let the native portion of the plugin +// know that it can start scheduling handling messages. + await backgroundChannel.invokeMethod('FcmDartService#initialized'); +} + /// Implementation of the Firebase Cloud Messaging API for Flutter. /// /// Your app should call [requestNotificationPermissions] first and then @@ -30,6 +71,7 @@ class FirebaseMessaging { final Platform _platform; MessageHandler _onMessage; + MessageHandler _onBackgroundMessage; MessageHandler _onLaunch; MessageHandler _onResume; @@ -59,6 +101,7 @@ class FirebaseMessaging { /// Sets up [MessageHandler] for incoming messages. void configure({ MessageHandler onMessage, + MessageHandler onBackgroundMessage, MessageHandler onLaunch, MessageHandler onResume, }) { @@ -67,6 +110,20 @@ class FirebaseMessaging { _onResume = onResume; _channel.setMethodCallHandler(_handleMethod); _channel.invokeMethod('configure'); + if (onBackgroundMessage != null) { + _onBackgroundMessage = onBackgroundMessage; + final CallbackHandle backgroundSetupHandle = + PluginUtilities.getCallbackHandle(fcmSetupBackgroundChannel); + final CallbackHandle backgroundMessageHandle = + PluginUtilities.getCallbackHandle(_onBackgroundMessage); + _channel.invokeMethod( + 'FcmDartService#start', + { + 'setupHandle': backgroundSetupHandle.toRawHandle(), + 'backgroundHandle': backgroundMessageHandle.toRawHandle() + }, + ); + } } final StreamController _tokenStreamController = diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart index c0a47ba57366..c95e011acd15 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -15,10 +15,12 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); MockMethodChannel mockChannel; + MockMethodChannel mockBackgroundChannel; FirebaseMessaging firebaseMessaging; setUp(() { mockChannel = MockMethodChannel(); + mockBackgroundChannel = MockMethodChannel(); firebaseMessaging = FirebaseMessaging.private( mockChannel, FakePlatform(operatingSystem: 'ios')); }); @@ -166,6 +168,12 @@ void main() { verify(mockChannel.invokeMethod('setAutoInitEnabled', false)); }); + + test('setupBackgroundCallback', () { + fcmSetupBackgroundChannel(backgroundChannel: mockBackgroundChannel); + verify( + mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); + }); } class MockMethodChannel extends Mock implements MethodChannel {} From 3fe9656129b5eb4dc52a6f6f00227899e3a17a1d Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Mon, 26 Aug 2019 15:09:44 -0700 Subject: [PATCH 2/8] Wrap chanel result logic in LatchResult --- .../FlutterFirebaseMessagingService.java | 18 +--------- .../firebasemessaging/LatchResult.java | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java index 97d898dcc8f2..502f0ab82e3a 100644 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java +++ b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java @@ -263,23 +263,7 @@ private static void executeDartCallbackInBackgroundIsolate( // If another thread is waiting, then wake that thread when the callback returns a result. MethodChannel.Result result = null; if (latch != null) { - result = - new MethodChannel.Result() { - @Override - public void success(Object result) { - latch.countDown(); - } - - @Override - public void error(String errorCode, String errorMessage, Object errorDetails) { - latch.countDown(); - } - - @Override - public void notImplemented() { - latch.countDown(); - } - }; + result = new LatchResult(latch).getResult(); } Map args = new HashMap<>(); diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java new file mode 100644 index 000000000000..4d5a25cf324f --- /dev/null +++ b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java @@ -0,0 +1,35 @@ +package io.flutter.plugins.firebasemessaging; + +import java.util.concurrent.CountDownLatch; + +import io.flutter.plugin.common.MethodChannel; + +public class LatchResult { + + private MethodChannel.Result result; + + public LatchResult(final CountDownLatch latch) { + result = new MethodChannel.Result() { + @Override + public void success(Object result) { + latch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + latch.countDown(); + } + + @Override + public void notImplemented() { + latch.countDown(); + } + }; + } + + public MethodChannel.Result getResult() { + return result; + } + + +} From 91e0fd61edf0e63df4a4a51438caa540dc709c7c Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Mon, 26 Aug 2019 15:59:00 -0700 Subject: [PATCH 3/8] address comments --- packages/firebase_messaging/CHANGELOG.md | 4 + packages/firebase_messaging/README.md | 5 ++ .../FlutterFirebaseMessagingService.java | 66 ++++++++-------- .../firebasemessaging/LatchResult.java | 38 +++++----- .../lib/firebase_messaging.dart | 76 +++++++++---------- packages/firebase_messaging/pubspec.yaml | 2 +- .../test/firebase_messaging_test.dart | 3 +- 7 files changed, 101 insertions(+), 93 deletions(-) diff --git a/packages/firebase_messaging/CHANGELOG.md b/packages/firebase_messaging/CHANGELOG.md index e8448c875253..a818b23895c8 100644 --- a/packages/firebase_messaging/CHANGELOG.md +++ b/packages/firebase_messaging/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.1.5 + +* Enable background message handling on Android. + ## 5.1.4 * Update documentation to reflect new repository location. diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index fa6558c34024..14afbc9673b5 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -56,6 +56,11 @@ Note: When you are debugging on Android, use a device or AVD with Google Play se ``` #### Optionally handle background messages +>Background message handling is intended to be performed quickly. Do not perform +long running tasks as they may not be allowed to finish by the Android system. +See [Background Execution Limits](https://developer.android.com/about/versions/oreo/background) +for more. + By default background messaging is not enabled. To handle messages in the background: 1. Add an Application.java class to your app diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java index 502f0ab82e3a..3b88b98b2c6b 100644 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java +++ b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java @@ -44,37 +44,37 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { private static final String BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY = "background_message_callback"; - // TODO(kroikie): make sIsIsolateRunning per-instance, not static. - private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false); + // TODO(kroikie): make isIsolateRunning per-instance, not static. + private static AtomicBoolean isIsolateRunning = new AtomicBoolean(false); /** Background Dart execution context. */ - private static FlutterNativeView sBackgroundFlutterView; + private static FlutterNativeView backgroundFlutterView; - private static MethodChannel sBackgroundChannel; + private static MethodChannel backgroundChannel; - private static Long sBackgroundMessageHandle; + private static Long backgroundMessageHandle; - private static List sBackgroundMessageQueue = + private static List backgroundMessageQueue = Collections.synchronizedList(new LinkedList()); - private static PluginRegistry.PluginRegistrantCallback sPluginRegistrantCallback; + private static PluginRegistry.PluginRegistrantCallback pluginRegistrantCallback; private static final String TAG = "FlutterFcmService"; - private static Context sBackgroundContext; + private static Context backgroundContext; @Override public void onCreate() { super.onCreate(); - sBackgroundContext = getApplicationContext(); - FlutterMain.ensureInitializationComplete(sBackgroundContext, null); + backgroundContext = getApplicationContext(); + FlutterMain.ensureInitializationComplete(backgroundContext, null); // If background isolate is not running start it. - if (!sIsIsolateRunning.get()) { - SharedPreferences p = sBackgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); + if (!isIsolateRunning.get()) { + SharedPreferences p = backgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); long callbackHandle = p.getLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, 0); - startBackgroundIsolate(sBackgroundContext, callbackHandle); + startBackgroundIsolate(backgroundContext, callbackHandle); } } @@ -94,8 +94,8 @@ public void onMessageReceived(final RemoteMessage remoteMessage) { } else { // If background isolate is not running yet, put message in queue and it will be handled // when the isolate starts. - if (!sIsIsolateRunning.get()) { - sBackgroundMessageQueue.add(remoteMessage); + if (!isIsolateRunning.get()) { + backgroundMessageQueue.add(remoteMessage); } else { final CountDownLatch latch = new CountDownLatch(1); new Handler(getMainLooper()) @@ -151,17 +151,17 @@ public static void startBackgroundIsolate(Context context, long callbackHandle) // Note that we're passing `true` as the second argument to our // FlutterNativeView constructor. This specifies the FlutterNativeView // as a background view and does not create a drawing surface. - sBackgroundFlutterView = new FlutterNativeView(context, true); - if (appBundlePath != null && !sIsIsolateRunning.get()) { - if (sPluginRegistrantCallback == null) { + backgroundFlutterView = new FlutterNativeView(context, true); + if (appBundlePath != null && !isIsolateRunning.get()) { + if (pluginRegistrantCallback == null) { throw new RuntimeException("PluginRegistrantCallback is not set."); } FlutterRunArguments args = new FlutterRunArguments(); args.bundlePath = appBundlePath; args.entrypoint = flutterCallback.callbackName; args.libraryPath = flutterCallback.callbackLibraryPath; - sBackgroundFlutterView.runFromBundle(args); - sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); + backgroundFlutterView.runFromBundle(args); + pluginRegistrantCallback.registerWith(backgroundFlutterView.getPluginRegistry()); } } @@ -170,15 +170,15 @@ public static void startBackgroundIsolate(Context context, long callbackHandle) * Dart side once all background initialization is complete via `FcmDartService#initialized`. */ public static void onInitialized() { - sIsIsolateRunning.set(true); - synchronized (sBackgroundMessageQueue) { + isIsolateRunning.set(true); + synchronized (backgroundMessageQueue) { // Handle all the messages received before the Dart isolate was // initialized, then clear the queue. - Iterator i = sBackgroundMessageQueue.iterator(); + Iterator i = backgroundMessageQueue.iterator(); while (i.hasNext()) { - executeDartCallbackInBackgroundIsolate(sBackgroundContext, i.next(), null); + executeDartCallbackInBackgroundIsolate(backgroundContext, i.next(), null); } - sBackgroundMessageQueue.clear(); + backgroundMessageQueue.clear(); } } @@ -189,7 +189,7 @@ public static void onInitialized() { * @param channel Background method channel. */ public static void setBackgroundChannel(MethodChannel channel) { - sBackgroundChannel = channel; + backgroundChannel = channel; } /** @@ -201,7 +201,7 @@ public static void setBackgroundChannel(MethodChannel channel) { * @param handle Handle representing the Dart side method that will handle background messages. */ public static void setBackgroundMessageHandle(Context context, Long handle) { - sBackgroundMessageHandle = handle; + backgroundMessageHandle = handle; // Store background message handle in shared preferences so it can be retrieved // by other application instances. @@ -255,7 +255,7 @@ public static Long getBackgroundMessageHandle(Context context) { */ private static void executeDartCallbackInBackgroundIsolate( Context context, RemoteMessage remoteMessage, final CountDownLatch latch) { - if (sBackgroundChannel == null) { + if (backgroundChannel == null) { throw new RuntimeException( "setBackgroundChannel was not called before messages came in, exiting."); } @@ -268,10 +268,10 @@ private static void executeDartCallbackInBackgroundIsolate( Map args = new HashMap<>(); Map messageData = new HashMap<>(); - if (sBackgroundMessageHandle == null) { - sBackgroundMessageHandle = getBackgroundMessageHandle(context); + if (backgroundMessageHandle == null) { + backgroundMessageHandle = getBackgroundMessageHandle(context); } - args.put("handle", sBackgroundMessageHandle); + args.put("handle", backgroundMessageHandle); if (remoteMessage.getData() != null) { messageData.put("data", remoteMessage.getData()); @@ -282,7 +282,7 @@ private static void executeDartCallbackInBackgroundIsolate( args.put("message", messageData); - sBackgroundChannel.invokeMethod("handleBackgroundMessage", args, result); + backgroundChannel.invokeMethod("handleBackgroundMessage", args, result); } /** @@ -292,7 +292,7 @@ private static void executeDartCallbackInBackgroundIsolate( * @param callback Application class which implements PluginRegistrantCallback. */ public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) { - sPluginRegistrantCallback = callback; + pluginRegistrantCallback = callback; } /** diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java index 4d5a25cf324f..7c4c9dfaeafa 100644 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java +++ b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java @@ -1,35 +1,33 @@ package io.flutter.plugins.firebasemessaging; -import java.util.concurrent.CountDownLatch; - import io.flutter.plugin.common.MethodChannel; +import java.util.concurrent.CountDownLatch; public class LatchResult { private MethodChannel.Result result; public LatchResult(final CountDownLatch latch) { - result = new MethodChannel.Result() { - @Override - public void success(Object result) { - latch.countDown(); - } - - @Override - public void error(String errorCode, String errorMessage, Object errorDetails) { - latch.countDown(); - } - - @Override - public void notImplemented() { - latch.countDown(); - } - }; + result = + new MethodChannel.Result() { + @Override + public void success(Object result) { + latch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + latch.countDown(); + } + + @Override + public void notImplemented() { + latch.countDown(); + } + }; } public MethodChannel.Result getResult() { return result; } - - } diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 794aa9980a70..ecf02ab4b972 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -13,44 +13,6 @@ import 'package:platform/platform.dart'; typedef Future MessageHandler(Map message); -/// Setup method channel to handle Firebase Cloud Messages received while -/// the Flutter app is not active. The handle for this method is generated -/// and passed to the Android side so that the background isolate knows where -/// to send background messages for processing. -/// -/// Your app should never call this method directly, this is only for use -/// by the firebase_messaging plugin to setup background message handling. -@visibleForTesting -Future fcmSetupBackgroundChannel( - {MethodChannel backgroundChannel = const MethodChannel( - 'plugins.flutter.io/firebase_messaging_background')}) async { - // Setup Flutter state needed for MethodChannels. - WidgetsFlutterBinding.ensureInitialized(); - - // This is where the magic happens and we handle background events from the - // native portion of the plugin. - backgroundChannel.setMethodCallHandler((MethodCall call) async { - if (call.method == 'handleBackgroundMessage') { - final CallbackHandle handle = - CallbackHandle.fromRawHandle(call.arguments['handle']); - final Function handlerFunction = - PluginUtilities.getCallbackFromHandle(handle); - try { - await handlerFunction( - Map.from(call.arguments['message'])); - } catch (e) { - print('Unable to handle incoming background message.'); - print(e); - } - return Future.value(); - } - }); - -// Once we've finished initializing, let the native portion of the plugin -// know that it can start scheduling handling messages. - await backgroundChannel.invokeMethod('FcmDartService#initialized'); -} - /// Implementation of the Firebase Cloud Messaging API for Flutter. /// /// Your app should call [requestNotificationPermissions] first and then @@ -58,6 +20,44 @@ Future fcmSetupBackgroundChannel( class FirebaseMessaging { factory FirebaseMessaging() => _instance; + /// Setup method channel to handle Firebase Cloud Messages received while + /// the Flutter app is not active. The handle for this method is generated + /// and passed to the Android side so that the background isolate knows where + /// to send background messages for processing. + /// + /// Your app should never call this method directly, this is only for use + /// by the firebase_messaging plugin to setup background message handling. + @visibleForTesting + static void fcmSetupBackgroundChannel( + {MethodChannel backgroundChannel = const MethodChannel( + 'plugins.flutter.io/firebase_messaging_background')}) async { + // Setup Flutter state needed for MethodChannels. + WidgetsFlutterBinding.ensureInitialized(); + + // This is where the magic happens and we handle background events from the + // native portion of the plugin. + backgroundChannel.setMethodCallHandler((MethodCall call) async { + if (call.method == 'handleBackgroundMessage') { + final CallbackHandle handle = + CallbackHandle.fromRawHandle(call.arguments['handle']); + final Function handlerFunction = + PluginUtilities.getCallbackFromHandle(handle); + try { + await handlerFunction( + Map.from(call.arguments['message'])); + } catch (e) { + print('Unable to handle incoming background message.'); + print(e); + } + return Future.value(); + } + }); + + // Once we've finished initializing, let the native portion of the plugin + // know that it can start scheduling handling messages. + backgroundChannel.invokeMethod('FcmDartService#initialized'); + } + @visibleForTesting FirebaseMessaging.private(MethodChannel channel, Platform platform) : _channel = channel, diff --git a/packages/firebase_messaging/pubspec.yaml b/packages/firebase_messaging/pubspec.yaml index aaf0a873c4a5..df38e5dbfa5b 100644 --- a/packages/firebase_messaging/pubspec.yaml +++ b/packages/firebase_messaging/pubspec.yaml @@ -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 homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging -version: 5.1.4 +version: 5.1.5 flutter: plugin: diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart index c95e011acd15..23cdf6030f07 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -170,7 +170,8 @@ void main() { }); test('setupBackgroundCallback', () { - fcmSetupBackgroundChannel(backgroundChannel: mockBackgroundChannel); + FirebaseMessaging.fcmSetupBackgroundChannel( + backgroundChannel: mockBackgroundChannel); verify( mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); }); From 1af6ba6d560ba65c5582c72d68a05dcf861303e0 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Mon, 26 Aug 2019 20:26:00 -0700 Subject: [PATCH 4/8] fix analyze issues --- .../lib/firebase_messaging.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index ecf02ab4b972..0598a5a8a901 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -20,6 +20,15 @@ typedef Future MessageHandler(Map message); class FirebaseMessaging { factory FirebaseMessaging() => _instance; + @visibleForTesting + FirebaseMessaging.private(MethodChannel channel, Platform platform) + : _channel = channel, + _platform = platform; + + static final FirebaseMessaging _instance = FirebaseMessaging.private( + const MethodChannel('plugins.flutter.io/firebase_messaging'), + const LocalPlatform()); + /// Setup method channel to handle Firebase Cloud Messages received while /// the Flutter app is not active. The handle for this method is generated /// and passed to the Android side so that the background isolate knows where @@ -39,9 +48,9 @@ class FirebaseMessaging { backgroundChannel.setMethodCallHandler((MethodCall call) async { if (call.method == 'handleBackgroundMessage') { final CallbackHandle handle = - CallbackHandle.fromRawHandle(call.arguments['handle']); + CallbackHandle.fromRawHandle(call.arguments['handle']); final Function handlerFunction = - PluginUtilities.getCallbackFromHandle(handle); + PluginUtilities.getCallbackFromHandle(handle); try { await handlerFunction( Map.from(call.arguments['message'])); @@ -58,15 +67,6 @@ class FirebaseMessaging { backgroundChannel.invokeMethod('FcmDartService#initialized'); } - @visibleForTesting - FirebaseMessaging.private(MethodChannel channel, Platform platform) - : _channel = channel, - _platform = platform; - - static final FirebaseMessaging _instance = FirebaseMessaging.private( - const MethodChannel('plugins.flutter.io/firebase_messaging'), - const LocalPlatform()); - final MethodChannel _channel; final Platform _platform; From 37d4156277076d1c7a95244ceeb8d17d6d33d846 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Mon, 26 Aug 2019 20:42:16 -0700 Subject: [PATCH 5/8] fix formatting --- packages/firebase_messaging/lib/firebase_messaging.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 0598a5a8a901..3986a71fd658 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -48,9 +48,9 @@ class FirebaseMessaging { backgroundChannel.setMethodCallHandler((MethodCall call) async { if (call.method == 'handleBackgroundMessage') { final CallbackHandle handle = - CallbackHandle.fromRawHandle(call.arguments['handle']); + CallbackHandle.fromRawHandle(call.arguments['handle']); final Function handlerFunction = - PluginUtilities.getCallbackFromHandle(handle); + PluginUtilities.getCallbackFromHandle(handle); try { await handlerFunction( Map.from(call.arguments['message'])); From 44c501f936c66c5cad65740fd6c589e1fe57ea0b Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 5 Sep 2019 13:24:02 -0700 Subject: [PATCH 6/8] Make fcmSetupBackgroundChannel top-level --- .../lib/firebase_messaging.dart | 76 +++++++++---------- .../test/firebase_messaging_test.dart | 2 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 3986a71fd658..bccd14931cbe 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -13,6 +13,44 @@ import 'package:platform/platform.dart'; typedef Future MessageHandler(Map message); +/// Setup method channel to handle Firebase Cloud Messages received while +/// the Flutter app is not active. The handle for this method is generated +/// and passed to the Android side so that the background isolate knows where +/// to send background messages for processing. +/// +/// Your app should never call this method directly, this is only for use +/// by the firebase_messaging plugin to setup background message handling. +@visibleForTesting +void fcmSetupBackgroundChannel( + {MethodChannel backgroundChannel = const MethodChannel( + 'plugins.flutter.io/firebase_messaging_background')}) async { + // Setup Flutter state needed for MethodChannels. + WidgetsFlutterBinding.ensureInitialized(); + + // This is where the magic happens and we handle background events from the + // native portion of the plugin. + backgroundChannel.setMethodCallHandler((MethodCall call) async { + if (call.method == 'handleBackgroundMessage') { + final CallbackHandle handle = + CallbackHandle.fromRawHandle(call.arguments['handle']); + final Function handlerFunction = + PluginUtilities.getCallbackFromHandle(handle); + try { + await handlerFunction( + Map.from(call.arguments['message'])); + } catch (e) { + print('Unable to handle incoming background message.'); + print(e); + } + return Future.value(); + } + }); + + // Once we've finished initializing, let the native portion of the plugin + // know that it can start scheduling handling messages. + backgroundChannel.invokeMethod('FcmDartService#initialized'); +} + /// Implementation of the Firebase Cloud Messaging API for Flutter. /// /// Your app should call [requestNotificationPermissions] first and then @@ -29,44 +67,6 @@ class FirebaseMessaging { const MethodChannel('plugins.flutter.io/firebase_messaging'), const LocalPlatform()); - /// Setup method channel to handle Firebase Cloud Messages received while - /// the Flutter app is not active. The handle for this method is generated - /// and passed to the Android side so that the background isolate knows where - /// to send background messages for processing. - /// - /// Your app should never call this method directly, this is only for use - /// by the firebase_messaging plugin to setup background message handling. - @visibleForTesting - static void fcmSetupBackgroundChannel( - {MethodChannel backgroundChannel = const MethodChannel( - 'plugins.flutter.io/firebase_messaging_background')}) async { - // Setup Flutter state needed for MethodChannels. - WidgetsFlutterBinding.ensureInitialized(); - - // This is where the magic happens and we handle background events from the - // native portion of the plugin. - backgroundChannel.setMethodCallHandler((MethodCall call) async { - if (call.method == 'handleBackgroundMessage') { - final CallbackHandle handle = - CallbackHandle.fromRawHandle(call.arguments['handle']); - final Function handlerFunction = - PluginUtilities.getCallbackFromHandle(handle); - try { - await handlerFunction( - Map.from(call.arguments['message'])); - } catch (e) { - print('Unable to handle incoming background message.'); - print(e); - } - return Future.value(); - } - }); - - // Once we've finished initializing, let the native portion of the plugin - // know that it can start scheduling handling messages. - backgroundChannel.invokeMethod('FcmDartService#initialized'); - } - final MethodChannel _channel; final Platform _platform; diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart index 23cdf6030f07..784d099084ba 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -170,7 +170,7 @@ void main() { }); test('setupBackgroundCallback', () { - FirebaseMessaging.fcmSetupBackgroundChannel( + fcmSetupBackgroundChannel( backgroundChannel: mockBackgroundChannel); verify( mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); From 438263ee253bf4ae9f3ce0ae6ed6667d2237b17e Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 5 Sep 2019 13:27:16 -0700 Subject: [PATCH 7/8] Make fcmSetupBackgroundChannel private --- packages/firebase_messaging/lib/firebase_messaging.dart | 5 ++--- .../firebase_messaging/test/firebase_messaging_test.dart | 7 ------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index bccd14931cbe..49699fd4bae9 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -20,8 +20,7 @@ typedef Future MessageHandler(Map message); /// /// Your app should never call this method directly, this is only for use /// by the firebase_messaging plugin to setup background message handling. -@visibleForTesting -void fcmSetupBackgroundChannel( +void _fcmSetupBackgroundChannel( {MethodChannel backgroundChannel = const MethodChannel( 'plugins.flutter.io/firebase_messaging_background')}) async { // Setup Flutter state needed for MethodChannels. @@ -113,7 +112,7 @@ class FirebaseMessaging { if (onBackgroundMessage != null) { _onBackgroundMessage = onBackgroundMessage; final CallbackHandle backgroundSetupHandle = - PluginUtilities.getCallbackHandle(fcmSetupBackgroundChannel); + PluginUtilities.getCallbackHandle(_fcmSetupBackgroundChannel); final CallbackHandle backgroundMessageHandle = PluginUtilities.getCallbackHandle(_onBackgroundMessage); _channel.invokeMethod( diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart index 784d099084ba..0535dbd602ca 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -168,13 +168,6 @@ void main() { verify(mockChannel.invokeMethod('setAutoInitEnabled', false)); }); - - test('setupBackgroundCallback', () { - fcmSetupBackgroundChannel( - backgroundChannel: mockBackgroundChannel); - verify( - mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); - }); } class MockMethodChannel extends Mock implements MethodChannel {} From e762755c994279091b67947eb1cf6590a6cadd23 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 5 Sep 2019 13:42:37 -0700 Subject: [PATCH 8/8] Fix analyze errors --- packages/firebase_messaging/README.md | 4 ++-- packages/firebase_messaging/test/firebase_messaging_test.dart | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index 14afbc9673b5..4e126949b929 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -96,12 +96,12 @@ By default background messaging is not enabled. To handle messages in the backgr Future myBackgroundMessageHandler(Map message) { if (message.containsKey('data')) { // Handle data message - dynamic data = message['data']; + final dynamic data = message['data']; } if (message.containsKey('notification')) { // Handle notification message - dynamic notification = message['notification']; + final dynamic notification = message['notification']; } // Or do other work. diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart index 0535dbd602ca..c0a47ba57366 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -15,12 +15,10 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); MockMethodChannel mockChannel; - MockMethodChannel mockBackgroundChannel; FirebaseMessaging firebaseMessaging; setUp(() { mockChannel = MockMethodChannel(); - mockBackgroundChannel = MockMethodChannel(); firebaseMessaging = FirebaseMessaging.private( mockChannel, FakePlatform(operatingSystem: 'ios')); });