From fb18e66fa35811ab675b1b9e6e9d3d49f0da5457 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 24 Jul 2019 13:49:50 -0700 Subject: [PATCH 01/15] Add support for handling messages in background --- packages/firebase_messaging/README.md | 67 +++++ .../FirebaseMessagingPlugin.java | 32 ++- .../FlutterFirebaseMessagingService.java | 244 +++++++++++++++++- .../android/app/src/main/AndroidManifest.xml | 2 +- .../firebasemessagingexample/Application.java | 20 ++ .../lib/firebase_messaging.dart | 42 +++ 6 files changed, 401 insertions(+), 6 deletions(-) create mode 100644 packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/Application.java diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index fced940d7b7f..263654ad9eeb 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -54,7 +54,74 @@ 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 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 work with other plugins, eg: write to RTDB. + FirebaseDatabase.instance.reference().child('foo').set('bar'); + return Future.value(); +} +``` +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); + }, + ); +``` ### 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..307563b24b55 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 @@ -19,6 +19,8 @@ import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.RemoteMessage; + +import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -26,7 +28,9 @@ import io.flutter.plugin.common.PluginRegistry.NewIntentListener; import io.flutter.plugin.common.PluginRegistry.Registrar; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** FirebaseMessagingPlugin */ @@ -41,9 +45,17 @@ 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/android_fcm_background", + JSONMethodCodec.INSTANCE); 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 +111,25 @@ private Map parseRemoteMessage(RemoteMessage message) { @Override public void onMethodCall(final MethodCall call, final Result result) { - if ("configure".equals(call.method)) { + if ("FcmDartService.start".equals(call.method)) { + long callbackHandle = 0; + long onMessageHandle = 0; + try { + List callbacks = ((ArrayList) call.arguments); + callbackHandle = callbacks.get(0); + onMessageHandle = callbacks.get(1); + } catch (Exception e) { + Log.e(TAG, "There was an exception when getting callback handle from dart side"); + e.printStackTrace(); + } + FlutterFirebaseMessagingService.setBackgroundSetupHandle(this.registrar.context(), callbackHandle); + FlutterFirebaseMessagingService.startBackgroundIsolate(this.registrar.context(), callbackHandle); + FlutterFirebaseMessagingService.setBackgroundMessageHandle(this.registrar.context(), onMessageHandle); + 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..04b733946daf 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,11 +4,35 @@ 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 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; + +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; + public class FlutterFirebaseMessagingService extends FirebaseMessagingService { public static final String ACTION_REMOTE_MESSAGE = @@ -18,16 +42,80 @@ 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(); + + Context context = getApplicationContext(); + sBackgroundContext = context; + FlutterMain.ensureInitializationComplete(context, null); + + // If background isolate is not running start it. + if (!sIsIsolateRunning.get()) { + SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); + long callbackHandle = p.getLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, 0); + startBackgroundIsolate(context, 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() { + Log.d(TAG, "executing dart callback"); + executeDartCallbackInBackgroundIsolate(FlutterFirebaseMessagingService.this, remoteMessage, latch); + } + }); + try { + latch.await(); + } catch (InterruptedException ex) { + Log.i(TAG, "Exception waiting to execute Dart callback", ex); + } + } + } } /** @@ -42,4 +130,152 @@ public void onNewToken(String token) { intent.putExtra(EXTRA_TOKEN, token); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } + + public static void startBackgroundIsolate(Context context, long callbackHandle) { + FlutterMain.ensureInitializationComplete(context, null); + String mAppBundlePath = 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 (mAppBundlePath != null && !sIsIsolateRunning.get()) { + if (sPluginRegistrantCallback == null) { + // throw new PluginRegistrantException(); + Log.d(TAG, "Registrant callback is null"); + return; + } + FlutterRunArguments args = new FlutterRunArguments(); + args.bundlePath = mAppBundlePath; + args.entrypoint = flutterCallback.callbackName; + args.libraryPath = flutterCallback.callbackLibraryPath; + sBackgroundFlutterView.runFromBundle(args); + Log.d(TAG, sBackgroundFlutterView.getPluginRegistry().toString()); + sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); + } + } + + 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(); + } + } + + public static void setBackgroundChannel(MethodChannel channel) { + sBackgroundChannel = channel; + } + + public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) { + sPluginRegistrantCallback = callback; + } + + 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(); + } + + public static void setBackgroundSetupHandle(Context context, long callbackHandle) { + // 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_SETUP_CALLBACK_HANDLE_KEY, callbackHandle).apply(); + } + + public static Long getOnMessageCallbackHandle(Context context) { + return context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0) + .getLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, 0); + } + + private static void executeDartCallbackInBackgroundIsolate(Context context, + RemoteMessage remoteMessage, final CountDownLatch latch) { + if (sBackgroundChannel == null) { + Log.e( + TAG, + "setBackgroundChannel was not called before messages came in, exiting."); + return; + } + + // 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<>(); + Log.d(TAG, "Sending background handle 1: " + sBackgroundMessageHandle); + if (sBackgroundMessageHandle == null) { + sBackgroundMessageHandle = getOnMessageCallbackHandle(context); + } + Log.d(TAG, "Sending background handle 2: " + sBackgroundMessageHandle); + 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("", args, result); + } + + // TODO(kroikie): Find a better way to determine application state. + 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 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/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml index adaa7b419adb..a1bb65cfe7ae 100644 --- a/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ - + MessageHandler(Map message); +const String _backgroundName = 'plugins.flutter.io/android_fcm_background'; + +void _fcmSetupCallback() { + const MethodChannel _channel = + MethodChannel(_backgroundName, JSONMethodCodec()); + + // 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. + _channel.setMethodCallHandler((MethodCall call) async { + final CallbackHandle handle = CallbackHandle.fromRawHandle(call.arguments['handle']); + final Function handlerFunction = PluginUtilities.getCallbackFromHandle(handle); + await handlerFunction(call.arguments['message']); + return Future.value(); + }); + + // Once we've finished initializing, let the native portion of the plugin + // know that it can start scheduling handling messages. + _channel.invokeMethod('FcmDartService.initialized'); +} + /// Implementation of the Firebase Cloud Messaging API for Flutter. /// /// Your app should call [requestNotificationPermissions] first and then @@ -30,6 +56,7 @@ class FirebaseMessaging { final Platform _platform; MessageHandler _onMessage; + MessageHandler _onBackgroundMessage; MessageHandler _onLaunch; MessageHandler _onResume; @@ -59,6 +86,7 @@ class FirebaseMessaging { /// Sets up [MessageHandler] for incoming messages. void configure({ MessageHandler onMessage, + MessageHandler onBackgroundMessage, MessageHandler onLaunch, MessageHandler onResume, }) { @@ -67,6 +95,20 @@ class FirebaseMessaging { _onResume = onResume; _channel.setMethodCallHandler(_handleMethod); _channel.invokeMethod('configure'); + if (_onBackgroundMessage != null) { + _onBackgroundMessage = onBackgroundMessage; + final CallbackHandle backgroundSetupHandle = + PluginUtilities.getCallbackHandle(_fcmSetupCallback); + final CallbackHandle backgroundMessageHandle = + PluginUtilities.getCallbackHandle(_onBackgroundMessage); + _channel.invokeMethod( + 'FcmDartService.start', + [ + backgroundSetupHandle.toRawHandle(), + backgroundMessageHandle.toRawHandle() + ], + ); + } } final StreamController _tokenStreamController = From 46a36a2cf8a96029abc9f393803cf25e8f1b01ee Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 25 Jul 2019 11:11:48 -0700 Subject: [PATCH 02/15] fix formatting --- .../FirebaseMessagingPlugin.java | 10 +++-- .../FlutterFirebaseMessagingService.java | 42 +++++++++---------- .../lib/firebase_messaging.dart | 10 +++-- 3 files changed, 33 insertions(+), 29 deletions(-) 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 307563b24b55..318086e6ffae 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 @@ -19,7 +19,6 @@ import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.RemoteMessage; - import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -122,9 +121,12 @@ public void onMethodCall(final MethodCall call, final Result result) { Log.e(TAG, "There was an exception when getting callback handle from dart side"); e.printStackTrace(); } - FlutterFirebaseMessagingService.setBackgroundSetupHandle(this.registrar.context(), callbackHandle); - FlutterFirebaseMessagingService.startBackgroundIsolate(this.registrar.context(), callbackHandle); - FlutterFirebaseMessagingService.setBackgroundMessageHandle(this.registrar.context(), onMessageHandle); + FlutterFirebaseMessagingService.setBackgroundSetupHandle( + this.registrar.context(), callbackHandle); + FlutterFirebaseMessagingService.startBackgroundIsolate( + this.registrar.context(), callbackHandle); + FlutterFirebaseMessagingService.setBackgroundMessageHandle( + this.registrar.context(), onMessageHandle); result.success(true); } else if ("FcmDartService.initialized".equals(call.method)) { FlutterFirebaseMessagingService.onInitialized(); 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 04b733946daf..948f96c16118 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 @@ -12,11 +12,15 @@ 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; @@ -26,13 +30,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -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; - public class FlutterFirebaseMessagingService extends FirebaseMessagingService { public static final String ACTION_REMOTE_MESSAGE = @@ -44,7 +41,8 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { 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"; + 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); @@ -56,7 +54,8 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { private static Long sBackgroundMessageHandle; - private static List sBackgroundMessageQueue = Collections.synchronizedList(new LinkedList()); + private static List sBackgroundMessageQueue = + Collections.synchronizedList(new LinkedList()); private static PluginRegistry.PluginRegistrantCallback sPluginRegistrantCallback; @@ -105,8 +104,8 @@ public void onMessageReceived(final RemoteMessage remoteMessage) { new Runnable() { @Override public void run() { - Log.d(TAG, "executing dart callback"); - executeDartCallbackInBackgroundIsolate(FlutterFirebaseMessagingService.this, remoteMessage, latch); + executeDartCallbackInBackgroundIsolate( + FlutterFirebaseMessagingService.this, remoteMessage, latch); } }); try { @@ -199,16 +198,15 @@ public static void setBackgroundSetupHandle(Context context, long callbackHandle } public static Long getOnMessageCallbackHandle(Context context) { - return context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0) + return context + .getSharedPreferences(SHARED_PREFERENCES_KEY, 0) .getLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, 0); } - private static void executeDartCallbackInBackgroundIsolate(Context context, - RemoteMessage remoteMessage, final CountDownLatch latch) { + private static void executeDartCallbackInBackgroundIsolate( + Context context, RemoteMessage remoteMessage, final CountDownLatch latch) { if (sBackgroundChannel == null) { - Log.e( - TAG, - "setBackgroundChannel was not called before messages came in, exiting."); + Log.e(TAG, "setBackgroundChannel was not called before messages came in, exiting."); return; } @@ -257,14 +255,16 @@ public void notImplemented() { // TODO(kroikie): Find a better way to determine application state. public static boolean isApplicationForeground(Context context) { - KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + 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); + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List list; diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 4cb68a6a938a..c063cb439d89 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -25,8 +25,10 @@ void _fcmSetupCallback() { // This is where the magic happens and we handle background events from the // native portion of the plugin. _channel.setMethodCallHandler((MethodCall call) async { - final CallbackHandle handle = CallbackHandle.fromRawHandle(call.arguments['handle']); - final Function handlerFunction = PluginUtilities.getCallbackFromHandle(handle); + final CallbackHandle handle = + CallbackHandle.fromRawHandle(call.arguments['handle']); + final Function handlerFunction = + PluginUtilities.getCallbackFromHandle(handle); await handlerFunction(call.arguments['message']); return Future.value(); }); @@ -98,9 +100,9 @@ class FirebaseMessaging { if (_onBackgroundMessage != null) { _onBackgroundMessage = onBackgroundMessage; final CallbackHandle backgroundSetupHandle = - PluginUtilities.getCallbackHandle(_fcmSetupCallback); + PluginUtilities.getCallbackHandle(_fcmSetupCallback); final CallbackHandle backgroundMessageHandle = - PluginUtilities.getCallbackHandle(_onBackgroundMessage); + PluginUtilities.getCallbackHandle(_onBackgroundMessage); _channel.invokeMethod( 'FcmDartService.start', [ From dd6cbaec5f62ff7840609f4e4fa471c65bfca5bf Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Mon, 29 Jul 2019 17:08:06 -0700 Subject: [PATCH 03/15] Prepare for review --- .../FirebaseMessagingPlugin.java | 14 ++++++------ .../FlutterFirebaseMessagingService.java | 22 +++++++------------ .../android/app/src/main/AndroidManifest.xml | 2 +- .../firebasemessagingexample/Application.java | 20 ----------------- 4 files changed, 16 insertions(+), 42 deletions(-) delete mode 100644 packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/Application.java 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 318086e6ffae..3d8b21e10f07 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 @@ -111,22 +111,22 @@ private Map parseRemoteMessage(RemoteMessage message) { @Override public void onMethodCall(final MethodCall call, final Result result) { if ("FcmDartService.start".equals(call.method)) { - long callbackHandle = 0; - long onMessageHandle = 0; + long setupCallbackHandle = 0; + long backgroundMessageHandle = 0; try { List callbacks = ((ArrayList) call.arguments); - callbackHandle = callbacks.get(0); - onMessageHandle = callbacks.get(1); + setupCallbackHandle = callbacks.get(0); + backgroundMessageHandle = callbacks.get(1); } catch (Exception e) { Log.e(TAG, "There was an exception when getting callback handle from dart side"); e.printStackTrace(); } FlutterFirebaseMessagingService.setBackgroundSetupHandle( - this.registrar.context(), callbackHandle); + this.registrar.context(), setupCallbackHandle); FlutterFirebaseMessagingService.startBackgroundIsolate( - this.registrar.context(), callbackHandle); + this.registrar.context(), setupCallbackHandle); FlutterFirebaseMessagingService.setBackgroundMessageHandle( - this.registrar.context(), onMessageHandle); + this.registrar.context(), backgroundMessageHandle); result.success(true); } else if ("FcmDartService.initialized".equals(call.method)) { FlutterFirebaseMessagingService.onInitialized(); 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 948f96c16118..3103b230f5d1 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 @@ -67,15 +67,14 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { public void onCreate() { super.onCreate(); - Context context = getApplicationContext(); - sBackgroundContext = context; - FlutterMain.ensureInitializationComplete(context, null); + sBackgroundContext = getApplicationContext(); + FlutterMain.ensureInitializationComplete(sBackgroundContext, null); // If background isolate is not running start it. if (!sIsIsolateRunning.get()) { - SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); + SharedPreferences p = sBackgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); long callbackHandle = p.getLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, 0); - startBackgroundIsolate(context, callbackHandle); + startBackgroundIsolate(sBackgroundContext, callbackHandle); } } @@ -146,16 +145,13 @@ public static void startBackgroundIsolate(Context context, long callbackHandle) sBackgroundFlutterView = new FlutterNativeView(context, true); if (mAppBundlePath != null && !sIsIsolateRunning.get()) { if (sPluginRegistrantCallback == null) { - // throw new PluginRegistrantException(); - Log.d(TAG, "Registrant callback is null"); - return; + throw new RuntimeException("PluginRegistrantCallback is not set."); } FlutterRunArguments args = new FlutterRunArguments(); args.bundlePath = mAppBundlePath; args.entrypoint = flutterCallback.callbackName; args.libraryPath = flutterCallback.callbackLibraryPath; sBackgroundFlutterView.runFromBundle(args); - Log.d(TAG, sBackgroundFlutterView.getPluginRegistry().toString()); sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); } } @@ -191,7 +187,7 @@ public static void setBackgroundMessageHandle(Context context, Long handle) { } public static void setBackgroundSetupHandle(Context context, long callbackHandle) { - // Store background message handle in shared preferences so it can be retrieved + // 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, callbackHandle).apply(); @@ -206,8 +202,8 @@ public static Long getOnMessageCallbackHandle(Context context) { private static void executeDartCallbackInBackgroundIsolate( Context context, RemoteMessage remoteMessage, final CountDownLatch latch) { if (sBackgroundChannel == null) { - Log.e(TAG, "setBackgroundChannel was not called before messages came in, exiting."); - return; + 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. @@ -234,11 +230,9 @@ public void notImplemented() { Map args = new HashMap<>(); Map messageData = new HashMap<>(); - Log.d(TAG, "Sending background handle 1: " + sBackgroundMessageHandle); if (sBackgroundMessageHandle == null) { sBackgroundMessageHandle = getOnMessageCallbackHandle(context); } - Log.d(TAG, "Sending background handle 2: " + sBackgroundMessageHandle); args.put("handle", sBackgroundMessageHandle); if (remoteMessage.getData() != null) { diff --git a/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml index a1bb65cfe7ae..adaa7b419adb 100644 --- a/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ - + Date: Tue, 13 Aug 2019 11:14:27 -0700 Subject: [PATCH 04/15] Update packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java Co-Authored-By: Collin Jackson --- .../plugins/firebasemessaging/FirebaseMessagingPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3d8b21e10f07..045b56b36bc4 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 @@ -118,7 +118,7 @@ public void onMethodCall(final MethodCall call, final Result result) { setupCallbackHandle = callbacks.get(0); backgroundMessageHandle = callbacks.get(1); } catch (Exception e) { - Log.e(TAG, "There was an exception when getting callback handle from dart side"); + Log.e(TAG, "There was an exception when getting callback handle from Dart side"); e.printStackTrace(); } FlutterFirebaseMessagingService.setBackgroundSetupHandle( From 3c85f6051518a234956b63dc820e061328059dbb Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 14 Aug 2019 16:45:27 -0700 Subject: [PATCH 05/15] Changes addressing comments --- packages/firebase_messaging/README.md | 125 +++++++++--------- .../FirebaseMessagingPlugin.java | 28 ++-- .../FlutterFirebaseMessagingService.java | 93 +++++++++++-- .../lib/firebase_messaging.dart | 69 +++++----- .../test/firebase_messaging_test.dart | 7 + 5 files changed, 208 insertions(+), 114 deletions(-) diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index 263654ad9eeb..55ab50844f93 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -60,68 +60,73 @@ By default background messaging is not enabled. To handle messages in the backgr 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); - } -} -``` + ``` + 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 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 work with other plugins, eg: write to RTDB. - FirebaseDatabase.instance.reference().child('foo').set('bar'); - return Future.value(); -} -``` + ``` + + ``` +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 work with other plugins, eg: write to RTDB. + FirebaseDatabase.instance.reference().child('foo').set('bar'); + return Future.value(); + } + ``` + 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); - }, - ); -``` + ``` + _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 3d8b21e10f07..e3ab350e3f3b 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 @@ -19,7 +19,6 @@ import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.RemoteMessage; -import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -27,9 +26,7 @@ import io.flutter.plugin.common.PluginRegistry.NewIntentListener; import io.flutter.plugin.common.PluginRegistry.Registrar; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; /** FirebaseMessagingPlugin */ @@ -47,8 +44,7 @@ public static void registerWith(Registrar registrar) { final MethodChannel backgroundCallbackChannel = new MethodChannel( registrar.messenger(), - "plugins.flutter.io/android_fcm_background", - JSONMethodCodec.INSTANCE); + "plugins.flutter.io/android_fcm_background"); final FirebaseMessagingPlugin plugin = new FirebaseMessagingPlugin(registrar, channel); registrar.addNewIntentListener(plugin); channel.setMethodCallHandler(plugin); @@ -110,13 +106,25 @@ private Map parseRemoteMessage(RemoteMessage message) { @Override public void onMethodCall(final MethodCall call, final Result result) { - if ("FcmDartService.start".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 { - List callbacks = ((ArrayList) call.arguments); - setupCallbackHandle = callbacks.get(0); - backgroundMessageHandle = callbacks.get(1); + 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(); @@ -128,7 +136,7 @@ public void onMethodCall(final MethodCall call, final Result result) { FlutterFirebaseMessagingService.setBackgroundMessageHandle( this.registrar.context(), backgroundMessageHandle); result.success(true); - } else if ("FcmDartService.initialized".equals(call.method)) { + } else if ("FcmDartService#initialized".equals(call.method)) { FlutterFirebaseMessagingService.onInitialized(); result.success(true); } else if ("configure".equals(call.method)) { 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 3103b230f5d1..0de3905bf320 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 @@ -129,9 +129,18 @@ public void onNewToken(String 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 mAppBundlePath = FlutterMain.findAppBundlePath(context); + String appBundlePath = FlutterMain.findAppBundlePath(); FlutterCallbackInformation flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); if (flutterCallback == null) { @@ -143,12 +152,12 @@ public static void startBackgroundIsolate(Context context, long callbackHandle) // FlutterNativeView constructor. This specifies the FlutterNativeView // as a background view and does not create a drawing surface. sBackgroundFlutterView = new FlutterNativeView(context, true); - if (mAppBundlePath != null && !sIsIsolateRunning.get()) { + if (appBundlePath != null && !sIsIsolateRunning.get()) { if (sPluginRegistrantCallback == null) { throw new RuntimeException("PluginRegistrantCallback is not set."); } FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = mAppBundlePath; + args.bundlePath = appBundlePath; args.entrypoint = flutterCallback.callbackName; args.libraryPath = flutterCallback.callbackLibraryPath; sBackgroundFlutterView.runFromBundle(args); @@ -156,6 +165,11 @@ public static void startBackgroundIsolate(Context context, long callbackHandle) } } + /** + * 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) { @@ -169,14 +183,26 @@ public static void onInitialized() { } } + /** + * 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; } - public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) { - sPluginRegistrantCallback = callback; - } - + /** + * 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; @@ -186,19 +212,51 @@ public static void setBackgroundMessageHandle(Context context, Long handle) { prefs.edit().putLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, handle).apply(); } - public static void setBackgroundSetupHandle(Context context, long callbackHandle) { + /** + * 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, callbackHandle).apply(); + prefs.edit().putLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, setupBackgroundHandle).apply(); } - public static Long getOnMessageCallbackHandle(Context context) { + /** + * 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) { @@ -231,7 +289,7 @@ public void notImplemented() { Map args = new HashMap<>(); Map messageData = new HashMap<>(); if (sBackgroundMessageHandle == null) { - sBackgroundMessageHandle = getOnMessageCallbackHandle(context); + sBackgroundMessageHandle = getBackgroundMessageHandle(context); } args.put("handle", sBackgroundMessageHandle); @@ -244,11 +302,20 @@ public void notImplemented() { args.put("message", messageData); - sBackgroundChannel.invokeMethod("", args, result); + sBackgroundChannel.invokeMethod("handleBackgroundMessage", args, result); } + /** + * 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. - public static boolean isApplicationForeground(Context context) { + private static boolean isApplicationForeground(Context context) { KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index c063cb439d89..1583302b71bd 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -13,36 +13,41 @@ import 'package:platform/platform.dart'; typedef Future MessageHandler(Map message); -const String _backgroundName = 'plugins.flutter.io/android_fcm_background'; - -void _fcmSetupCallback() { - const MethodChannel _channel = - MethodChannel(_backgroundName, JSONMethodCodec()); - - // 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. - _channel.setMethodCallHandler((MethodCall call) async { - final CallbackHandle handle = - CallbackHandle.fromRawHandle(call.arguments['handle']); - final Function handlerFunction = - PluginUtilities.getCallbackFromHandle(handle); - await handlerFunction(call.arguments['message']); - return Future.value(); - }); - - // Once we've finished initializing, let the native portion of the plugin - // know that it can start scheduling handling messages. - _channel.invokeMethod('FcmDartService.initialized'); -} - /// Implementation of the Firebase Cloud Messaging API for Flutter. /// /// Your app should call [requestNotificationPermissions] first and then /// register handlers for incoming messages with [configure]. class FirebaseMessaging { + /// 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 Future fcmSetupBackgroundChannel(MethodChannel backgroundChannel) 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); + await handlerFunction(call.arguments['message']); + 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'); + } + factory FirebaseMessaging() => _instance; @visibleForTesting @@ -100,15 +105,17 @@ class FirebaseMessaging { if (_onBackgroundMessage != null) { _onBackgroundMessage = onBackgroundMessage; final CallbackHandle backgroundSetupHandle = - PluginUtilities.getCallbackHandle(_fcmSetupCallback); + PluginUtilities.getCallbackHandle(() { + fcmSetupBackgroundChannel(const MethodChannel('plugins.flutter.io/android_fcm_background')); + }); final CallbackHandle backgroundMessageHandle = PluginUtilities.getCallbackHandle(_onBackgroundMessage); _channel.invokeMethod( - 'FcmDartService.start', - [ - backgroundSetupHandle.toRawHandle(), - backgroundMessageHandle.toRawHandle() - ], + 'FcmDartService#start', + { + 'setupHandle': backgroundSetupHandle.toRawHandle(), + 'backgroundHandle': backgroundMessageHandle.toRawHandle() + }, ); } } diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart index a4e6d7c04756..16de96bf6390 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -12,10 +12,12 @@ import 'package:test/test.dart'; void main() { MockMethodChannel mockChannel; + MockMethodChannel mockBackgroundChannel; FirebaseMessaging firebaseMessaging; setUp(() { mockChannel = MockMethodChannel(); + mockBackgroundChannel = MockMethodChannel(); firebaseMessaging = FirebaseMessaging.private( mockChannel, FakePlatform(operatingSystem: 'ios')); }); @@ -163,6 +165,11 @@ void main() { verify(mockChannel.invokeMethod('setAutoInitEnabled', false)); }); + + test('setupBackgroundCallback', () { + FirebaseMessaging.fcmSetupBackgroundChannel(mockBackgroundChannel); + verify(mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); + }); } class MockMethodChannel extends Mock implements MethodChannel {} From 52f0f05898581589fe983185ac386aa74104ad44 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 14 Aug 2019 16:47:04 -0700 Subject: [PATCH 06/15] fix formatting --- .../FirebaseMessagingPlugin.java | 4 +- .../FlutterFirebaseMessagingService.java | 66 +++++++++---------- .../lib/firebase_messaging.dart | 12 ++-- .../test/firebase_messaging_test.dart | 3 +- 4 files changed, 41 insertions(+), 44 deletions(-) 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 e3ab350e3f3b..31169507b348 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 @@ -42,9 +42,7 @@ 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/android_fcm_background"); + new MethodChannel(registrar.messenger(), "plugins.flutter.io/android_fcm_background"); final FirebaseMessagingPlugin plugin = new FirebaseMessagingPlugin(registrar, channel); registrar.addNewIntentListener(plugin); channel.setMethodCallHandler(plugin); 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 0de3905bf320..6e2bb1f61440 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 @@ -130,13 +130,13 @@ public void onNewToken(String token) { } /** - * 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. + * 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. + * @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); @@ -166,9 +166,8 @@ public static void startBackgroundIsolate(Context context, long callbackHandle) } /** - * 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`. + * 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); @@ -194,14 +193,12 @@ public static void setBackgroundChannel(MethodChannel 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`. + * 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. + * @param handle Handle representing the Dart side method that will handle background messages. */ public static void setBackgroundMessageHandle(Context context, Long handle) { sBackgroundMessageHandle = handle; @@ -213,14 +210,14 @@ public static void setBackgroundMessageHandle(Context context, Long handle) { } /** - * 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`. + * 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. + * @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 @@ -230,12 +227,11 @@ public static void setBackgroundSetupHandle(Context context, long setupBackgroun } /** - * 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. + * 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. @@ -248,14 +244,14 @@ public static Long getBackgroundMessageHandle(Context context) { /** * 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. + * 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. + * @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) { @@ -306,13 +302,13 @@ public void notImplemented() { } /** - * 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. + * 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. + * false otherwise. */ // TODO(kroikie): Find a better way to determine application state. private static boolean isApplicationForeground(Context context) { diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 1583302b71bd..72f51cae055d 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -26,7 +26,8 @@ class FirebaseMessaging { /// 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 Future fcmSetupBackgroundChannel(MethodChannel backgroundChannel) async { + static Future fcmSetupBackgroundChannel( + MethodChannel backgroundChannel) async { // Setup Flutter state needed for MethodChannels. WidgetsFlutterBinding.ensureInitialized(); @@ -35,9 +36,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); await handlerFunction(call.arguments['message']); return Future.value(); } @@ -106,8 +107,9 @@ class FirebaseMessaging { _onBackgroundMessage = onBackgroundMessage; final CallbackHandle backgroundSetupHandle = PluginUtilities.getCallbackHandle(() { - fcmSetupBackgroundChannel(const MethodChannel('plugins.flutter.io/android_fcm_background')); - }); + fcmSetupBackgroundChannel( + const MethodChannel('plugins.flutter.io/android_fcm_background')); + }); 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 16de96bf6390..40ea98ddf912 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -168,7 +168,8 @@ void main() { test('setupBackgroundCallback', () { FirebaseMessaging.fcmSetupBackgroundChannel(mockBackgroundChannel); - verify(mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); + verify( + mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); }); } From 8c6b11e5f97391d151640b6ba4e5a3856c24c467 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 15 Aug 2019 10:22:58 -0700 Subject: [PATCH 07/15] fix analyze errors --- .../lib/firebase_messaging.dart | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 72f51cae055d..cc04d59b552f 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -18,6 +18,18 @@ typedef Future MessageHandler(Map message); /// Your app should call [requestNotificationPermissions] first and then /// register handlers for incoming messages with [configure]. 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 @@ -36,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); await handlerFunction(call.arguments['message']); return Future.value(); } @@ -49,17 +61,6 @@ class FirebaseMessaging { await backgroundChannel.invokeMethod('FcmDartService#initialized'); } - 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()); - final MethodChannel _channel; final Platform _platform; From 52a19bacbeadac4d0e989091d3ad58e4da3368b1 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 15 Aug 2019 10:40:50 -0700 Subject: [PATCH 08/15] fix formatting --- packages/firebase_messaging/lib/firebase_messaging.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index cc04d59b552f..34c587141e40 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -18,7 +18,6 @@ typedef Future MessageHandler(Map message); /// Your app should call [requestNotificationPermissions] first and then /// register handlers for incoming messages with [configure]. class FirebaseMessaging { - factory FirebaseMessaging() => _instance; @visibleForTesting @@ -48,9 +47,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); await handlerFunction(call.arguments['message']); return Future.value(); } From 300db8a48110a5149954947ce9c21aa53b4baca4 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Fri, 16 Aug 2019 15:45:29 -0700 Subject: [PATCH 09/15] return missing method from MessagingService --- .../FlutterFirebaseMessagingService.java | 10 +++ .../lib/firebase_messaging.dart | 76 ++++++++++--------- .../test/firebase_messaging_test.dart | 2 +- 3 files changed, 51 insertions(+), 37 deletions(-) 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 6e2bb1f61440..5efa0231fed3 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 @@ -301,6 +301,16 @@ public void notImplemented() { 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 diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index 34c587141e40..d62b85279b9d 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 +Future fcmSetupBackgroundChannel( + {MethodChannel backgroundChannel = const MethodChannel( + 'plugins.flutter.io/android_fcm_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 @@ -29,37 +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 Future fcmSetupBackgroundChannel( - MethodChannel backgroundChannel) 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); - await handlerFunction(call.arguments['message']); - 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'); - } - final MethodChannel _channel; final Platform _platform; @@ -103,13 +110,10 @@ class FirebaseMessaging { _onResume = onResume; _channel.setMethodCallHandler(_handleMethod); _channel.invokeMethod('configure'); - if (_onBackgroundMessage != null) { + if (onBackgroundMessage != null) { _onBackgroundMessage = onBackgroundMessage; final CallbackHandle backgroundSetupHandle = - PluginUtilities.getCallbackHandle(() { - fcmSetupBackgroundChannel( - const MethodChannel('plugins.flutter.io/android_fcm_background')); - }); + 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 40ea98ddf912..057d178e4237 100644 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/test/firebase_messaging_test.dart @@ -167,7 +167,7 @@ void main() { }); test('setupBackgroundCallback', () { - FirebaseMessaging.fcmSetupBackgroundChannel(mockBackgroundChannel); + fcmSetupBackgroundChannel(backgroundChannel: mockBackgroundChannel); verify( mockBackgroundChannel.invokeMethod('FcmDartService#initialized')); }); From 699bbe4d68bb6856ff7e8ea458ec7ac5160a80e6 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Fri, 16 Aug 2019 15:51:34 -0700 Subject: [PATCH 10/15] fix formatting --- .../firebasemessaging/FlutterFirebaseMessagingService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5efa0231fed3..b73cfbb65321 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 @@ -302,8 +302,8 @@ public void notImplemented() { } /** - * Set the registrant callback. This is called by the app's Application class if - * background message handling is enabled. + * 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. */ From 14ceed7f524f227d197f74b7e6195524793c6db6 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Fri, 16 Aug 2019 16:13:28 -0700 Subject: [PATCH 11/15] revert to using available on stable --- .../firebasemessaging/FlutterFirebaseMessagingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b73cfbb65321..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 @@ -140,7 +140,7 @@ public void onNewToken(String token) { */ public static void startBackgroundIsolate(Context context, long callbackHandle) { FlutterMain.ensureInitializationComplete(context, null); - String appBundlePath = FlutterMain.findAppBundlePath(); + String appBundlePath = FlutterMain.findAppBundlePath(context); FlutterCallbackInformation flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); if (flutterCallback == null) { From 806ce881075405078a88057dae2d24d322886edd Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 21 Aug 2019 08:36:16 -0700 Subject: [PATCH 12/15] Update background channel name --- .../plugins/firebasemessaging/FirebaseMessagingPlugin.java | 2 +- packages/firebase_messaging/lib/firebase_messaging.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 38a35213aba0..42cd2f62eb8e 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 @@ -42,7 +42,7 @@ 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/android_fcm_background"); + new MethodChannel(registrar.messenger(), "plugins.flutter.io/firebase_messaging_background"); final FirebaseMessagingPlugin plugin = new FirebaseMessagingPlugin(registrar, channel); registrar.addNewIntentListener(plugin); channel.setMethodCallHandler(plugin); diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart index d62b85279b9d..794aa9980a70 100644 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ b/packages/firebase_messaging/lib/firebase_messaging.dart @@ -23,7 +23,7 @@ typedef Future MessageHandler(Map message); @visibleForTesting Future fcmSetupBackgroundChannel( {MethodChannel backgroundChannel = const MethodChannel( - 'plugins.flutter.io/android_fcm_background')}) async { + 'plugins.flutter.io/firebase_messaging_background')}) async { // Setup Flutter state needed for MethodChannels. WidgetsFlutterBinding.ensureInitialized(); From 121ba01a7e992d212ce84578ecb0271406f4c053 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 21 Aug 2019 08:38:20 -0700 Subject: [PATCH 13/15] fix formatting --- .../plugins/firebasemessaging/FirebaseMessagingPlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 42cd2f62eb8e..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 @@ -42,7 +42,8 @@ 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"); + new MethodChannel( + registrar.messenger(), "plugins.flutter.io/firebase_messaging_background"); final FirebaseMessagingPlugin plugin = new FirebaseMessagingPlugin(registrar, channel); registrar.addNewIntentListener(plugin); channel.setMethodCallHandler(plugin); From d300a8448fbb1d8e79b18f60bb2bdd50d252699a Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Wed, 21 Aug 2019 14:47:08 -0700 Subject: [PATCH 14/15] remove RTDB example from README.md --- packages/firebase_messaging/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index 55ab50844f93..32ebe278e367 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -99,9 +99,7 @@ By default background messaging is not enabled. To handle messages in the backgr dynamic notification = message['notification']; } - // Or do work with other plugins, eg: write to RTDB. - FirebaseDatabase.instance.reference().child('foo').set('bar'); - return Future.value(); + // Or do work other work. } ``` Note: the protocol of `data` and `notification` are in line with the From e7b521f228eac9ca430a8a6adca7d0cdeaa49e04 Mon Sep 17 00:00:00 2001 From: Arthur Thompson Date: Thu, 22 Aug 2019 06:31:00 -0700 Subject: [PATCH 15/15] fix typo --- packages/firebase_messaging/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index 32ebe278e367..47676faac17d 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -99,7 +99,7 @@ By default background messaging is not enabled. To handle messages in the backgr dynamic notification = message['notification']; } - // Or do work other work. + // Or do other work. } ``` Note: the protocol of `data` and `notification` are in line with the