Skip to content

Commit

Permalink
address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
kroikie committed Sep 4, 2019
1 parent 3fe9656 commit 91e0fd6
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 93 deletions.
4 changes: 4 additions & 0 deletions packages/firebase_messaging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 5.1.5

* Enable background message handling on Android.

## 5.1.4

* Update documentation to reflect new repository location.
Expand Down
5 changes: 5 additions & 0 deletions packages/firebase_messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ Note: When you are debugging on Android, use a device or AVD with Google Play se
```
#### Optionally handle background messages

>Background message handling is intended to be performed quickly. Do not perform
long running tasks as they may not be allowed to finish by the Android system.
See [Background Execution Limits](https://developer.android.com/about/versions/oreo/background)
for more.

By default background messaging is not enabled. To handle messages in the background:

1. Add an Application.java class to your app
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,37 @@ public class FlutterFirebaseMessagingService extends FirebaseMessagingService {
private static final String BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY =
"background_message_callback";

// TODO(kroikie): make sIsIsolateRunning per-instance, not static.
private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false);
// TODO(kroikie): make isIsolateRunning per-instance, not static.
private static AtomicBoolean isIsolateRunning = new AtomicBoolean(false);

/** Background Dart execution context. */
private static FlutterNativeView sBackgroundFlutterView;
private static FlutterNativeView backgroundFlutterView;

private static MethodChannel sBackgroundChannel;
private static MethodChannel backgroundChannel;

private static Long sBackgroundMessageHandle;
private static Long backgroundMessageHandle;

private static List<RemoteMessage> sBackgroundMessageQueue =
private static List<RemoteMessage> backgroundMessageQueue =
Collections.synchronizedList(new LinkedList<RemoteMessage>());

private static PluginRegistry.PluginRegistrantCallback sPluginRegistrantCallback;
private static PluginRegistry.PluginRegistrantCallback pluginRegistrantCallback;

private static final String TAG = "FlutterFcmService";

private static Context sBackgroundContext;
private static Context backgroundContext;

@Override
public void onCreate() {
super.onCreate();

sBackgroundContext = getApplicationContext();
FlutterMain.ensureInitializationComplete(sBackgroundContext, null);
backgroundContext = getApplicationContext();
FlutterMain.ensureInitializationComplete(backgroundContext, null);

// If background isolate is not running start it.
if (!sIsIsolateRunning.get()) {
SharedPreferences p = sBackgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
if (!isIsolateRunning.get()) {
SharedPreferences p = backgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
long callbackHandle = p.getLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, 0);
startBackgroundIsolate(sBackgroundContext, callbackHandle);
startBackgroundIsolate(backgroundContext, callbackHandle);
}
}

Expand All @@ -94,8 +94,8 @@ public void onMessageReceived(final RemoteMessage remoteMessage) {
} else {
// If background isolate is not running yet, put message in queue and it will be handled
// when the isolate starts.
if (!sIsIsolateRunning.get()) {
sBackgroundMessageQueue.add(remoteMessage);
if (!isIsolateRunning.get()) {
backgroundMessageQueue.add(remoteMessage);
} else {
final CountDownLatch latch = new CountDownLatch(1);
new Handler(getMainLooper())
Expand Down Expand Up @@ -151,17 +151,17 @@ public static void startBackgroundIsolate(Context context, long callbackHandle)
// Note that we're passing `true` as the second argument to our
// FlutterNativeView constructor. This specifies the FlutterNativeView
// as a background view and does not create a drawing surface.
sBackgroundFlutterView = new FlutterNativeView(context, true);
if (appBundlePath != null && !sIsIsolateRunning.get()) {
if (sPluginRegistrantCallback == null) {
backgroundFlutterView = new FlutterNativeView(context, true);
if (appBundlePath != null && !isIsolateRunning.get()) {
if (pluginRegistrantCallback == null) {
throw new RuntimeException("PluginRegistrantCallback is not set.");
}
FlutterRunArguments args = new FlutterRunArguments();
args.bundlePath = appBundlePath;
args.entrypoint = flutterCallback.callbackName;
args.libraryPath = flutterCallback.callbackLibraryPath;
sBackgroundFlutterView.runFromBundle(args);
sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry());
backgroundFlutterView.runFromBundle(args);
pluginRegistrantCallback.registerWith(backgroundFlutterView.getPluginRegistry());
}
}

Expand All @@ -170,15 +170,15 @@ public static void startBackgroundIsolate(Context context, long callbackHandle)
* Dart side once all background initialization is complete via `FcmDartService#initialized`.
*/
public static void onInitialized() {
sIsIsolateRunning.set(true);
synchronized (sBackgroundMessageQueue) {
isIsolateRunning.set(true);
synchronized (backgroundMessageQueue) {
// Handle all the messages received before the Dart isolate was
// initialized, then clear the queue.
Iterator<RemoteMessage> i = sBackgroundMessageQueue.iterator();
Iterator<RemoteMessage> i = backgroundMessageQueue.iterator();
while (i.hasNext()) {
executeDartCallbackInBackgroundIsolate(sBackgroundContext, i.next(), null);
executeDartCallbackInBackgroundIsolate(backgroundContext, i.next(), null);
}
sBackgroundMessageQueue.clear();
backgroundMessageQueue.clear();
}
}

Expand All @@ -189,7 +189,7 @@ public static void onInitialized() {
* @param channel Background method channel.
*/
public static void setBackgroundChannel(MethodChannel channel) {
sBackgroundChannel = channel;
backgroundChannel = channel;
}

/**
Expand All @@ -201,7 +201,7 @@ public static void setBackgroundChannel(MethodChannel channel) {
* @param handle Handle representing the Dart side method that will handle background messages.
*/
public static void setBackgroundMessageHandle(Context context, Long handle) {
sBackgroundMessageHandle = handle;
backgroundMessageHandle = handle;

// Store background message handle in shared preferences so it can be retrieved
// by other application instances.
Expand Down Expand Up @@ -255,7 +255,7 @@ public static Long getBackgroundMessageHandle(Context context) {
*/
private static void executeDartCallbackInBackgroundIsolate(
Context context, RemoteMessage remoteMessage, final CountDownLatch latch) {
if (sBackgroundChannel == null) {
if (backgroundChannel == null) {
throw new RuntimeException(
"setBackgroundChannel was not called before messages came in, exiting.");
}
Expand All @@ -268,10 +268,10 @@ private static void executeDartCallbackInBackgroundIsolate(

Map<String, Object> args = new HashMap<>();
Map<String, Object> messageData = new HashMap<>();
if (sBackgroundMessageHandle == null) {
sBackgroundMessageHandle = getBackgroundMessageHandle(context);
if (backgroundMessageHandle == null) {
backgroundMessageHandle = getBackgroundMessageHandle(context);
}
args.put("handle", sBackgroundMessageHandle);
args.put("handle", backgroundMessageHandle);

if (remoteMessage.getData() != null) {
messageData.put("data", remoteMessage.getData());
Expand All @@ -282,7 +282,7 @@ private static void executeDartCallbackInBackgroundIsolate(

args.put("message", messageData);

sBackgroundChannel.invokeMethod("handleBackgroundMessage", args, result);
backgroundChannel.invokeMethod("handleBackgroundMessage", args, result);
}

/**
Expand All @@ -292,7 +292,7 @@ private static void executeDartCallbackInBackgroundIsolate(
* @param callback Application class which implements PluginRegistrantCallback.
*/
public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) {
sPluginRegistrantCallback = callback;
pluginRegistrantCallback = callback;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
package io.flutter.plugins.firebasemessaging;

import java.util.concurrent.CountDownLatch;

import io.flutter.plugin.common.MethodChannel;
import java.util.concurrent.CountDownLatch;

public class LatchResult {

private MethodChannel.Result result;

public LatchResult(final CountDownLatch latch) {
result = new MethodChannel.Result() {
@Override
public void success(Object result) {
latch.countDown();
}

@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
latch.countDown();
}

@Override
public void notImplemented() {
latch.countDown();
}
};
result =
new MethodChannel.Result() {
@Override
public void success(Object result) {
latch.countDown();
}

@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
latch.countDown();
}

@Override
public void notImplemented() {
latch.countDown();
}
};
}

public MethodChannel.Result getResult() {
return result;
}


}
76 changes: 38 additions & 38 deletions packages/firebase_messaging/lib/firebase_messaging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,51 @@ import 'package:platform/platform.dart';

typedef Future<dynamic> MessageHandler(Map<String, dynamic> 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<void> fcmSetupBackgroundChannel(
{MethodChannel backgroundChannel = const MethodChannel(
'plugins.flutter.io/firebase_messaging_background')}) async {
// Setup Flutter state needed for MethodChannels.
WidgetsFlutterBinding.ensureInitialized();

// This is where the magic happens and we handle background events from the
// native portion of the plugin.
backgroundChannel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'handleBackgroundMessage') {
final CallbackHandle handle =
CallbackHandle.fromRawHandle(call.arguments['handle']);
final Function handlerFunction =
PluginUtilities.getCallbackFromHandle(handle);
try {
await handlerFunction(
Map<String, dynamic>.from(call.arguments['message']));
} catch (e) {
print('Unable to handle incoming background message.');
print(e);
}
return Future<void>.value();
}
});

// Once we've finished initializing, let the native portion of the plugin
// know that it can start scheduling handling messages.
await backgroundChannel.invokeMethod<void>('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 {
factory FirebaseMessaging() => _instance;

/// Setup method channel to handle Firebase Cloud Messages received while
/// the Flutter app is not active. The handle for this method is generated
/// and passed to the Android side so that the background isolate knows where
/// to send background messages for processing.
///
/// Your app should never call this method directly, this is only for use
/// by the firebase_messaging plugin to setup background message handling.
@visibleForTesting
static void fcmSetupBackgroundChannel(
{MethodChannel backgroundChannel = const MethodChannel(
'plugins.flutter.io/firebase_messaging_background')}) async {
// Setup Flutter state needed for MethodChannels.
WidgetsFlutterBinding.ensureInitialized();

// This is where the magic happens and we handle background events from the
// native portion of the plugin.
backgroundChannel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'handleBackgroundMessage') {
final CallbackHandle handle =
CallbackHandle.fromRawHandle(call.arguments['handle']);
final Function handlerFunction =
PluginUtilities.getCallbackFromHandle(handle);
try {
await handlerFunction(
Map<String, dynamic>.from(call.arguments['message']));
} catch (e) {
print('Unable to handle incoming background message.');
print(e);
}
return Future<void>.value();
}
});

// Once we've finished initializing, let the native portion of the plugin
// know that it can start scheduling handling messages.
backgroundChannel.invokeMethod<void>('FcmDartService#initialized');
}

@visibleForTesting
FirebaseMessaging.private(MethodChannel channel, Platform platform)
: _channel = channel,
Expand Down
2 changes: 1 addition & 1 deletion packages/firebase_messaging/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for Firebase Cloud Messaging, a cross-platform
messaging solution that lets you reliably deliver messages on Android and iOS.
author: Flutter Team <[email protected]>
homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging
version: 5.1.4
version: 5.1.5

flutter:
plugin:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ void main() {
});

test('setupBackgroundCallback', () {
fcmSetupBackgroundChannel(backgroundChannel: mockBackgroundChannel);
FirebaseMessaging.fcmSetupBackgroundChannel(
backgroundChannel: mockBackgroundChannel);
verify(
mockBackgroundChannel.invokeMethod<void>('FcmDartService#initialized'));
});
Expand Down

0 comments on commit 91e0fd6

Please sign in to comment.