Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(firebase_messaging): Support Android v2 embedding #3572

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/firebase_messaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
81 changes: 45 additions & 36 deletions packages/firebase_messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<app-name>/android/app/src/main/java/<app-organization-path>/`.

```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 `<app-name>/android/app/src/main/`.

```xml
<application android:name=".Application" ...>
```

1. Define a **TOP-LEVEL** or **STATIC** function to handle background messages

Expand Down Expand Up @@ -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 `<app-name>/android/app/src/main/java/<app-organization-path>/`.

```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 `<app-name>/android/app/src/main/`.

```xml
<application android:name=".Application" ...>
```

### iOS Integration

To integrate your plugin into the iOS part of your app, follow these steps:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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()) {
Expand Down Expand Up @@ -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());
}
}

Expand All @@ -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.
Expand Down Expand Up @@ -290,6 +309,10 @@ private static void executeDartCallbackInBackgroundIsolate(
* message handling is enabled.
*
* @param callback Application class which implements PluginRegistrantCallback.
* <p>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;
Expand Down
3 changes: 2 additions & 1 deletion packages/firebase_messaging/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down