diff --git a/packages/firebase_messaging/CHANGELOG.md b/packages/firebase_messaging/CHANGELOG.md index eca5a4dd55e1..959e9982fa9c 100644 --- a/packages/firebase_messaging/CHANGELOG.md +++ b/packages/firebase_messaging/CHANGELOG.md @@ -1,3 +1,12 @@ +## 8.0.0 + +* Add support for Android v2 embedding. +* **Breaking Change** This will cause applications with v2 embedding that declare the background handler and + call `FlutterFirebaseMessagingService.setPluginRegistrant` in their `Application.java` `onCreate` method + to crash on startup or when receiving background notifications. + To have this plugin work with these applications, you can read the updated README or + you can delete your `Application.java` and remove the `android:name=".Application"` reference in your manifest. + ## 7.0.2 - **FIX**: remove `platform` package usage (#3729). diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md index 14313e6b489e..d94d57133940 100644 --- a/packages/firebase_messaging/README.md +++ b/packages/firebase_messaging/README.md @@ -73,42 +73,6 @@ By default background messaging is not enabled. To handle messages in the backgr Note: you can find out what the latest version of the plugin is [here ("Cloud Messaging")](https://firebase.google.com/support/release-notes/android#latest_sdk_versions). -1. Add an `Application.java` class to your app in the same directory as your `MainActivity.java`. This is typically found in `/android/app/src/main/java//`. - - ```java - 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. In `Application.java`, make sure to change `package io.flutter.plugins.firebasemessagingexample;` to your package's identifier. Your package's identifier should be something like `com.domain.myapplication`. - - ```java - package com.domain.myapplication; - ``` - -1. Set name property of application in `AndroidManifest.xml`. This is typically found in `/android/app/src/main/`. - - ```xml - - ``` 1. Define a **TOP-LEVEL** or **STATIC** function to handle background messages @@ -155,6 +119,51 @@ By default background messaging is not enabled. To handle messages in the backgr so that it can be ready to receive messages as early as possible. See the [example app](https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging/example) for a demonstration. + +>If you are using a Flutter version prior to 1.12 or you are using v1 embedding, you need to complete additional steps. +For more information about v1/v2 embedding, check [Upgrading pre 1.12 Android](https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects). + +Additional steps for v1 embedding: + +1. Add an `Application.java` class to your app in the same directory as your `MainActivity.java`. This is typically found in `/android/app/src/main/java//`. + + ```java + 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); + } + } + ``` + + Note: Calls to `FlutterFirebaseMessagingService.setPluginRegistrant` while using v2 embedding will result in a build time error. + +1. In `Application.java`, make sure to change `package io.flutter.plugins.firebasemessagingexample;` to your package's identifier. Your package's identifier should be something like `com.domain.myapplication`. + + ```java + package com.domain.myapplication; + ``` + +1. Set name property of application in `AndroidManifest.xml`. This is typically found in `/android/app/src/main/`. + + ```xml + + ``` + ### iOS Integration To integrate your plugin into the iOS part of your app, follow these steps: 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 d475b9e0a3b8..6065e2069b95 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 @@ -54,12 +54,10 @@ public static void registerWith(Registrar registrar) { private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger) { this.applicationContext = context; channel = new MethodChannel(binaryMessenger, "plugins.flutter.io/firebase_messaging"); - final MethodChannel backgroundCallbackChannel = - new MethodChannel(binaryMessenger, "plugins.flutter.io/firebase_messaging_background"); - channel.setMethodCallHandler(this); - backgroundCallbackChannel.setMethodCallHandler(this); - FlutterFirebaseMessagingService.setBackgroundChannel(backgroundCallbackChannel); + + //Add reference to this class as the MethodCallHandler + FlutterFirebaseMessagingService.setFirebaseMessagingPlugin(this); // Register broadcast receiver IntentFilter intentFilter = new IntentFilter(); @@ -69,6 +67,21 @@ private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger manager.registerReceiver(this, intentFilter); } + public void initializeBackgroundMethodChannel(BinaryMessenger binaryMessenger) { + + // backgroundChannel is the channel responsible for receiving the following messages from + // the background isolate that was setup by this plugin: + // - "FcmDartService#initialized" + // + // This channel is also responsible for sending requests from Android to Dart to execute Dart + // callbacks in the background isolate. + final MethodChannel backgroundCallbackChannel = + new MethodChannel(binaryMessenger, "plugins.flutter.io/firebase_messaging_background"); + backgroundCallbackChannel.setMethodCallHandler(this); + + FlutterFirebaseMessagingService.setBackgroundChannel(backgroundCallbackChannel); + } + private void setActivity(Activity flutterActivity) { this.mainActivity = flutterActivity; } 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 bf9207649bbc..bf48d7887ae6 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 @@ -9,18 +9,21 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.AssetManager; 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.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartCallback; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; 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; @@ -47,8 +50,7 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { // TODO(kroikie): make isIsolateRunning per-instance, not static. private static AtomicBoolean isIsolateRunning = new AtomicBoolean(false); - /** Background Dart execution context. */ - private static FlutterNativeView backgroundFlutterView; + private static FlutterEngine backgroundFlutterEngine; private static MethodChannel backgroundChannel; @@ -63,12 +65,13 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService { private static Context backgroundContext; + private static FirebaseMessagingPlugin firebaseMessagingPlugin; + @Override public void onCreate() { super.onCreate(); backgroundContext = getApplicationContext(); - FlutterMain.ensureInitializationComplete(backgroundContext, null); // If background isolate is not running start it. if (!isIsolateRunning.get()) { @@ -139,29 +142,35 @@ public void onNewToken(String token) { * handling on the dart side. */ public static void startBackgroundIsolate(Context context, long callbackHandle) { - FlutterMain.ensureInitializationComplete(context, null); - String appBundlePath = FlutterMain.findAppBundlePath(); - FlutterCallbackInformation flutterCallback = - FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); - if (flutterCallback == null) { - Log.e(TAG, "Fatal: failed to find callback"); + if (backgroundFlutterEngine != null) { + Log.e(TAG, "Background isolate already started"); 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. - backgroundFlutterView = new FlutterNativeView(context, true); - if (appBundlePath != null) { - if (pluginRegistrantCallback == null) { - throw new RuntimeException("PluginRegistrantCallback is not set."); + String appBundlePath = FlutterMain.findAppBundlePath(); + AssetManager assets = context.getAssets(); + if (!isIsolateRunning.get()) { + backgroundFlutterEngine = new FlutterEngine(context); + + // We need to create an instance of `FlutterEngine` before looking up the + // callback. If we don't, the callback cache won't be initialized and the + // lookup will fail. + FlutterCallbackInformation flutterCallback = + FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + + DartExecutor executor = backgroundFlutterEngine.getDartExecutor(); + firebaseMessagingPlugin.initializeBackgroundMethodChannel(executor); + DartCallback dartCallback = new DartCallback(assets, appBundlePath, flutterCallback); + + executor.executeDartCallback(dartCallback); + + // The pluginRegistrantCallback should only be set in the V1 embedding as + // plugin registration is done via reflection in the V2 embedding. + // If set while using V2 embedding, the application will crash. + if (pluginRegistrantCallback != null) { + Log.d(TAG, "Proceeding with v1 embedding"); + pluginRegistrantCallback.registerWith(new ShimPluginRegistry(backgroundFlutterEngine)); } - FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = appBundlePath; - args.entrypoint = flutterCallback.callbackName; - args.libraryPath = flutterCallback.callbackLibraryPath; - backgroundFlutterView.runFromBundle(args); - pluginRegistrantCallback.registerWith(backgroundFlutterView.getPluginRegistry()); } } @@ -182,6 +191,16 @@ public static void onInitialized() { } } + /** + * Set the Firebase messaging plugin instance that is used to register method channels. This + * method is only called when the plugin registers. + * + * @param plugin Firebase messaging plugin instance. + */ + public static void setFirebaseMessagingPlugin(FirebaseMessagingPlugin plugin) { + firebaseMessagingPlugin = plugin; + } + /** * Set the method channel that is used for handling background messages. This method is only * called when the plugin registers. @@ -290,6 +309,10 @@ private static void executeDartCallbackInBackgroundIsolate( * message handling is enabled. * * @param callback Application class which implements PluginRegistrantCallback. + *

Note: this is only necessary for applications using the V1 engine embedding API as + * plugins are automatically registered via reflection in the V2 engine embedding API. If not + * set, messaging callbacks will not be able to utilize functionality from other plugins nor + * the background message handler. */ public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) { pluginRegistrantCallback = callback; diff --git a/packages/firebase_messaging/pubspec.yaml b/packages/firebase_messaging/pubspec.yaml index bc05fdf2f39c..df077ab73a2c 100644 --- a/packages/firebase_messaging/pubspec.yaml +++ b/packages/firebase_messaging/pubspec.yaml @@ -2,7 +2,8 @@ name: firebase_messaging description: Flutter plugin for Firebase Cloud Messaging, a cross-platform messaging solution that lets you reliably deliver messages on Android and iOS. homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging -version: 7.0.2 +version: 8.0.0 + flutter: plugin: