Reputation: 931
I have been reading various tutorials, other SO threads, as well as the official Android Developer and Firebase documentation to no avail. I've tried nearly everything and I'm running out of steam as well as time as I'm in the process of repairing a notification system that previously worked but no longer works.
I am using Azure Notification Hubs to distribute notifications to FCM among other Push Notification platforms. My FCM project targets only Android. My app is built in Xamarin.Forms using the latest NuGet package versions of all dependencies (Xamarin.Forms 5.x.x.x, Firebase.Messaging 122.0, etc.).
Currently, remote messages received while the app is running or backgrounded work flawlessly via a custom Service inheriting and implementing FirebaseMessagingService. Once the app is killed (task switcher -> swipe app away), upon sending further messages I start seeing Logcat messages with the following:
broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg= (has extras) }
I understand Google changed the way implicit receivers work in API 26 and above and my understanding is that this action (com.google.android.c2dm.intent.RECEIVE
) is not included in the exception list, so I am lost as to how the messages can be listened for and handled in the background. I have read in other threads as recently as July 2019 that FCM messages received while an app is killed are supposed to be sent directly to the notification tray. This cannot be a widespread problem as many applications send notifications while killed, so I'm hoping to get some current information to direct me to a solution.
Is this intent broadcast being cancelled because of the implicit receiver changes, or am I doing something else wrong?
I am testing on a OnePlus 7 Pro with Android 10, so I am wondering if maybe it's a battery optimization issue that others have mentioned on devices by OEMs such as Huawei and Xiaomi.
My app is targeting Android API level 29 with min API 21
I have enabled Direct Boot Awareness for my main activity as well as the receiver to ensure that the receiver intercepts intents for my app upon boot and before the user has opened the app:
<receiver android:directBootAware="true" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND" android:name=".NotificationReceiver">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="<package>" />
</intent-filter>
</receiver>
My main activity includes intent filters for being the launch activity:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
I request the following permissions:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
I have defined the following meta-data
tags in my manifest <application>
tag:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/..."/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/..." />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/..." />
These values work perfectly for received notifications while in background, so I know they are correctly configured.
Edit 1:
I have done more digging and debugging since posting and I do see that, without my custom BroadcastReceiver also listening for the c2dm.intent.RECEIVE action that my app:
logcat upon receiving FCM remote message while app is killed
*******FirebaseService partial code:
[Service(DirectBootAware = true, Exported = true, Enabled = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class MyFirebaseService : FirebaseMessagingService
{
public MyFirebaseService()
{
}
public override ComponentName StartService(Intent service)
{
Log.Info("GCM", $"MyFirebaseService started from intent {service}");
return base.StartService(service);
}
public override void OnMessageReceived(RemoteMessage message)
{
var notification = message.GetNotification();
Log.Info("GCM", $"Received remote message from FCM. Has notification: {notification != null}, has data: {message.Data != null}");
if (notification != null)
{
message.Data.TryGetValue("route", out string route);
SendNotification(notification.Title, notification.Body, route);
}
else
{
ParseDataNotification(message);
}
}
...
Upvotes: 3
Views: 5485
Reputation: 1
I was struggling on this issue for a few days, too. But when I just upload my app onto Google Play for Internal Test, then my app can handle FCM data message when app is closed.
Upvotes: 0
Reputation: 838
The @AndrewH solution worked for me. With one missing detail. The Firebase messaging service
will get called also when the app is killed. When the app is killed, only the constructor of the service will get called because internally firebase knows your app is killed. So, before you initialize any code that will handle notifications on foreground or any code that interacts with Xamarin forms
, you should check if Xamarin forms
has been initialized. For example:
if (Xamarin.Forms.Forms.IsInitialized)
{
// Do stuffs.
}
Otherwise your app will crash when it receives a push notification from killed state.
Also you should know that if the app is killed or Xamarin.Forms.Forms.IsInitialized == false
, you should not try to execute any code. Just leave it. Firebase will just show the notification for you. You will just handle the notification when the user click on the notification from the system tray in your MainActivity.OnCreate()
.
Upvotes: 0
Reputation: 931
I was able to resolve the issue. My FirebaseMessagingService implementation had a Dependency Injection call in the constructor which failed when the service was started in the background by the FirebaseIidInstanceReceiver. This caused the service to fail to start and did not generate Android notifications while the app was killed.
Since I've done a lot of digging and information on this topic is so fragmented and out of date, I'll try to compile what I know results in a working solution here:
Follow the steps here, notably setting up your FCM project and downloading the google-services.json
file.
Ensure your manifest declares the following permissions:
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
Add the following within your AndroidManifest <application>
tag to listen for message receives:
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
Optionally define defaults for notification channel, notification icon (must be white color only, allowing transparency), and notification icon color when the notification tray is expanded, also within the <application>
manifest tag:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/..."/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/..." />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/..." />
Create a custom class inheriting from FirebaseMessagingService
. In Xamarin.Forms, you will need the Xamarin.Firebase.Messaging NuGet package for this class. Within your implementation, you should override OnMessageReceived(RemoteMessage)
and add your application logic which will handle messages containing the notification
property in the foreground and messages with only the data
property in both the foreground and background. Your class should be decorated with the following attributes (note that DirectBootAware is optional; see below):
[Service(DirectBootAware = true, Exported = true, Enabled = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
If you wish to ensure that notifications can be received after a device reboot and before the device is unlocked, you may consider making your application and your FirebaseMessagingService implementation Direct Boot Aware (more here)
In your MainActivity, ensure a Notification Channel is created for devices running Android O or higher, and this method invoked at some point during OnCreate
:
private void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channelId = GetString(Resource.String./*res id here*/);
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
// Don't re-create the notification channel if we already created it
if (notificationManager.GetNotificationChannel(channelId) == null)
{
var channel = new NotificationChannel(channelId,
"<display name>",
NotificationImportance.Default);
notificationManager.CreateNotificationChannel(channel);
}
}
Add a ProGuard config file ("proguard.cfg") to your Android project to prevent the SDK linker from killing Google Play and Firebase libraries. Edit the Properties of this file in Visual Studio and set the Build Action to ProguardConfiguration
. Even if the option is missing from the dropdown list, Xamarin will recognize it. If you are using d8 and r8 instead of dx and ProGuard in your build, Xamarin will still use this config file and conform to the rules you define within.
# Keep commands are required to prevent the linker from killing dependencies not directly referenced in code
# See: https://forums.xamarin.com/discussion/95107/firebaseinstanceidreceiver-classnotfoundexception-when-receiving-notifications
-dontwarn com.google.android.gms.**
-keep class com.google.android.gms.** { *; }
-keep class com.google.firebase.** { *; }
Hope this helps and if I've missed anything I will update with further details.
Upvotes: 5