-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[firebase_messaging] Add support for handling messages in background #1900
Changes from 3 commits
fb18e66
46a36a2
dd6cbae
80abf02
3c85f60
52f0f05
db06ee3
8c6b11e
52a19ba
300db8a
699bbe4
14ceed7
806ce88
121ba01
d300a84
e7b521f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,7 +54,74 @@ Note: When you are debugging on Android, use a device or AVD with Google Play se | |
<category android:name="android.intent.category.DEFAULT" /> | ||
</intent-filter> | ||
``` | ||
#### 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` | ||
collinjackson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
<application android:name=".Application" ...> | ||
``` | ||
1. Define a top level method to handle background messages | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explicitly mention that this is a Dart method so that readers do not get confused? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
``` | ||
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) { | ||
if (message.containsKey('data')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you mention somewhere where a developer can find the protocol that is being used here? The reference to 'data' and 'notification' looks magical. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, it is in line with RemoteMessage fields. |
||
// Handle data message | ||
dynamic data = message['data']; | ||
} | ||
|
||
if (message.containsKey('notification')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can a message be both a "data" message and a "notification" message? If not, please consider using an if-else-if statement to make that explicit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RemoteMessages can contain both a data and notification part. How the Android SDK passes the message over to the developer (and Plugin) depends on the state of the app. |
||
// Handle notification message | ||
dynamic notification = message['notification']; | ||
} | ||
|
||
// Or do work with other plugins, eg: write to RTDB. | ||
FirebaseDatabase.instance.reference().child('foo').set('bar'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reference to another plugin might be confusing for the reader - it seems to come out of nowhere. Also, are you sure this works as expected? You're executing in a background isolate, which will have a different version of the FirebaseDatabase plugin than the main isolate. This line might suggest to readers that there is only 1 FirebaseDatabase plugin and that they're controlling it from this background isolate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example is to suggest that the developer is able to perform work in the background. In this case writing some data to RTDB. I assume the dev could use the shared preferences plugin to store some data locally. I have tried this and it does work as expected from my tests, is there some reason why getting an instance of RTDB (which is a singleton) in a different isolate would be an issue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the implementation of a plugin ensures that it shares resources across all plugin instances then things are fine. My concern here is that we're suggesting that you can interact with any plugin from here, but any plugin that does not setup its own singleton behavior would not work as expected... |
||
return Future<void>.value(); | ||
} | ||
``` | ||
1. Set `onBackgroundMessage` handler when calling `configure` | ||
kroikie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
_firebaseMessaging.configure( | ||
onMessage: (Map<String, dynamic> message) async { | ||
print("onMessage: $message"); | ||
_showItemDialog(message); | ||
}, | ||
onBackgroundMessage: myBackgroundMessageHandler, | ||
onLaunch: (Map<String, dynamic> message) async { | ||
print("onLaunch: $message"); | ||
_navigateToItemDetail(message); | ||
}, | ||
onResume: (Map<String, dynamic> message) async { | ||
print("onResume: $message"); | ||
_navigateToItemDetail(message); | ||
}, | ||
); | ||
``` | ||
|
||
### iOS Integration | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,14 +19,17 @@ | |
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; | ||
import io.flutter.plugin.common.MethodChannel.Result; | ||
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 +44,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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, why do we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is just a artifact from the alarm manager plugin I don't think it is necessary, the default codec should be sufficient. |
||
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 +110,28 @@ private Map<String, Object> parseRemoteMessage(RemoteMessage message) { | |
|
||
@Override | ||
public void onMethodCall(final MethodCall call, final Result result) { | ||
if ("configure".equals(call.method)) { | ||
if ("FcmDartService.start".equals(call.method)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency with other plugins that use method names to represent both a class and a method (e.g. storage), consider using Also if the class you're calling is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Used
kroikie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
long setupCallbackHandle = 0; | ||
long backgroundMessageHandle = 0; | ||
try { | ||
List<Long> callbacks = ((ArrayList<Long>) call.arguments); | ||
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"); | ||
kroikie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
e.printStackTrace(); | ||
} | ||
FlutterFirebaseMessagingService.setBackgroundSetupHandle( | ||
kroikie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.registrar.context(), setupCallbackHandle); | ||
FlutterFirebaseMessagingService.startBackgroundIsolate( | ||
this.registrar.context(), setupCallbackHandle); | ||
FlutterFirebaseMessagingService.setBackgroundMessageHandle( | ||
this.registrar.context(), backgroundMessageHandle); | ||
result.success(true); | ||
} else if ("FcmDartService.initialized".equals(call.method)) { | ||
FlutterFirebaseMessagingService.onInitialized(); | ||
result.success(true); | ||
} else if ("configure".equals(call.method)) { | ||
FirebaseInstanceId.getInstance() | ||
.getInstanceId() | ||
.addOnCompleteListener( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where in the App we should put this Java class, with the MainActivity or inside io.flutter.plugins?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JillyTaboga You should add it alongside your
MainActivity.java
file.