I'm following the tutorial here to set up Firebase Cloud Messaging for Android in my Xamarin.Forms application. This is an app that currently uses Google Cloud Messaging (GCM). I believe I followed all of the steps in the tutorial. However, my OnTokenRefresh
method or OnMessageReceived
method never fires.
In my Droid project, I have the packages Xamarin.Firebase.Common, Xamarin.Firebase.Iid, Xamarin.Firebase.Messaging, Xamarin.GooglePlayServices.Base, Xamarin.GooglePlayServices.Basement, Xamarin.GooglePlayServices.Tasks. I also have about 70 other packages (I didn't create this app). I removed any packages related to GCM, and removed GCM-related code. My google-services.json is set to GoogleServicesJson build action.
When I tried adding Console.Out.WriteLine("MainActivity InstanceID token: " + FirebaseInstanceId.Instance.Token);
to MainActivity's OnCreate
method to try to force the app to give me a token, it crashes with the error:
Java.Lang.IllegalStateException
Default FirebaseApp is not initialized in this process com.ebelinski.fakename. Make sure to call FirebaseApp.initializeApp(Context) first.
I've tried a bunch of different things and nothing seems to work. Below is my code:
WinterFirebaseIIDService.cs
using System.Collections.Generic;
using Android.App;
using WindowsAzure.Messaging;
using Firebase.Iid;
namespace FakeName.Droid {
[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class WinterFirebaseIIDService: FirebaseInstanceIdService {
NotificationHub hub;
public override void OnTokenRefresh() {
var refreshedToken = FirebaseInstanceId.Instance.Token;
AppLog.Checkpoint("WinterFirebaseIIDService FCM token: " + refreshedToken);
SendRegistrationToServer(refreshedToken);
}
void SendRegistrationToServer(string token) {
// Register with Notification Hubs
hub = new NotificationHub(AppConstants.NOTIFICATION_HUB_NAME,
AppConstants.NOTIFICATION_HUB_CONNECTION, this);
var tags = new List<string>() { };
var regID = hub.Register(token, tags.ToArray()).RegistrationId;
AppLog.Checkpoint("WinterFirebaseIIDService Successful registration of ID " + regID);
}
}
}
WinterFirebaseMessagingService
using System.Linq;
using Android.App;
using Firebase.Messaging;
namespace FakeName.Droid {
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class WinterFirebaseMessagingService: FirebaseMessagingService {
public override void OnMessageReceived(RemoteMessage message) {
AppLog.Checkpoint("WinterFirebaseMessagingService From: " + message.From);
if (message.GetNotification() != null) {
//These is how most messages will be received
AppLog.Checkpoint("WinterFirebaseMessagingService Notification Message Body: " + message.GetNotification().Body);
SendNotification(message.GetNotification().Body);
}
else {
//Only used for debugging payloads sent from the Azure portal
SendNotification(message.Data.Values.First());
}
}
void SendNotification(string messageBody) {
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ebelinski.fakename" android:versionName="1.1" android:versionCode="15">
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="28" />
<application android:label="Fake Name">
<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>
</application>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
AndroidMaifest.xml (the version in /Droid/obj/Debug/android/)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ebelinski.fakename" android:versionName="1.1" android:versionCode="15">
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="28" />
<permission android:name="com.ebelinski.fakename.permission.C2D_MESSAGE" />
<uses-permission android:name="com.ebelinski.fakename.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:name="android.hardware.location.network" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application android:label="Fake Name" android:name="android.support.multidex.MultiDexApplication" android:allowBackup="true" android:icon="@drawable/icon" android:debuggable="true">
<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="com.ebelinski.fakename" />
</intent-filter>
</receiver>
<activity android:configChanges="orientation|screenSize" android:icon="@drawable/icon" android:label="Fake Name" android:launchMode="singleTop" android:theme="@style/CustomTheme" android:name="md501b8064acb9363ced7fe7144b683c1bc.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:configChanges="orientation|screenSize" android:label="Notification Activity" android:name="md501b8064acb9363ced7fe7144b683c1bc.NotificationActivity" />
<service android:name="md501b8064acb9363ced7fe7144b683c1bc.BackgroundService">
<intent-filter>
<action android:name="FakeName.Droid.BackgroundService" />
</intent-filter>
</service>
<service android:name="md501b8064acb9363ced7fe7144b683c1bc.WinterFirebaseIIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service android:name="md501b8064acb9363ced7fe7144b683c1bc.WinterFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver android:enabled="true" android:exported="false" android:name="md51558244f76c53b6aeda52c8a337f2c37.PowerSaveModeBroadcastReceiver" />
<provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="2147483647" android:authorities="com.ebelinski.fakename.mono.MonoRuntimeProvider.__mono_init__" />
<!--suppress ExportedReceiver-->
<receiver android:name="mono.android.Seppuku">
<intent-filter>
<action android:name="mono.android.intent.action.SEPPUKU" />
<category android:name="mono.android.intent.category.SEPPUKU.com.ebelinski.fakename" />
</intent-filter>
</receiver>
<provider android:authorities="com.ebelinski.fakename.firebaseinitprovider" android:name="com.google.firebase.provider.FirebaseInitProvider" android:exported="false" android:initOrder="100" />
<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" />
<category android:name="com.ebelinski.fakename" />
</intent-filter>
</receiver>
<!-- Internal (not exported) receiver used by the app to start its own exported services
without risk of being spoofed. -->
<!-- FirebaseInstanceIdService performs security checks at runtime,
no need for explicit permissions despite exported="true" -->
<service android:name="com.google.firebase.iid.FirebaseInstanceIdService" android:exported="true">
<intent-filter android:priority="-500">
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<!-- FirebaseMessagingService performs security checks at runtime,
no need for explicit permissions despite exported="true" -->
<service android:name="com.google.firebase.messaging.FirebaseMessagingService" android:exported="true">
<intent-filter android:priority="-500">
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<activity android:name="com.google.android.gms.common.api.GoogleApiActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar" android:exported="false" />
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
</application>
<permission android:name="com.ebelinski.fakename.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.ebelinski.fakename.permission.C2D_MESSAGE" />
</manifest>
MainActivity.cs
using Android.App;
using Android.Content.PM;
using Android.OS;
using System.Globalization;
using Xamarin;
using FakeName.Infrastructure;
using FakeNameDroid.Infrastructure;
using Android.Content;
using System;
using FakeNameMessaging;
using FakeNameModels;
using Firebase.Iid;
using Firebase.Messaging;
using Firebase;
namespace FakeName.Droid
{
[Activity (Label = "FakeName", LaunchMode = LaunchMode.SingleTop, Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, Theme = "@style/CustomTheme")]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
// This will be set to true in the OnNewIntent method to notify the app should navigate to the notifications page after launching
private bool navigateToNotifications = false;
// Google API Project Number
public static MainActivity instance;
protected override void OnCreate (Bundle bundle)
{
AppLog.Checkpoint("MainActivity OnCreate");
instance = this;
base.OnCreate (bundle);
AppLog.Checkpoint("MainActivity Initializing Xamarin Forms");
global::Xamarin.Forms.Forms.Init (this, bundle);
// Set up the Translation service based on the current culture
Translate.Initialize (CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
SettingsPageModel.ShowManageNotificationsButton = true;
try
{
string notificationData = null;
if(bundle != null) {
notificationData = bundle.GetString("notification");
} else {
notificationData = Intent.GetStringExtra("notification");
}
if (!string.IsNullOrEmpty(notificationData)) {
AppLog.Checkpoint("MainActivity Notification Data Present in bundle");
navigateToNotifications = true;
} else {
AppLog.Checkpoint("MainActivity Notification Data not present in bundle");
}
}
catch (Exception ex)
{
AppLog.Checkpoint("MainActivity Exception getting notification data OnCreate");
AppLog.CaptureException ("MainActivity CheckForNotificationData", ex);
}
FillIoCContainer();
CreateNotificationChannel();
if (navigateToNotifications) {
LoadApplication (new App (NavigationDetailPage.Notifications));
// Reset flag
navigateToNotifications = false;
} else {
LoadApplication (new App ());
}
// NOTE: I added this even though it didn't say so in the tutorial...it doesn't work with or without it...
FirebaseApp app = FirebaseApp.InitializeApp(Android.App.Application.Context);
}
protected override void OnResume ()
{
base.OnResume();
AppLog.Checkpoint("MainActivity OnResume");
// NOTE: This is where the app crashes. It crashes even if I move this line to OnCreate.
Console.Out.WriteLine("MainActivity InstanceID token: " + FirebaseInstanceId.Instance.Token);
if (navigateToNotifications) {
AppLog.Checkpoint("MainActivity Navigating to notifications");
GalaSoft.MvvmLight.Messaging.Messenger.Default.Send (new NavigationMessageEvent (NavigationDetailPage.Notifications));
navigateToNotifications = false; // reset the value
}
}
/*
* The intent that calls MainActivity from a notification is actually a different intent than the one we built and won't
* have the extra data we put in it. This method 'OnNewIntent' catches our intent that was tied to that notification so
* we can get the extra data we put in it when the user click it.
*/
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
AppLog.Checkpoint("MainActivity OnNewIntent called");
try
{
var notificationData = intent.GetStringExtra("notification");
if (!string.IsNullOrEmpty(notificationData)) {
AppLog.Checkpoint("MainActivity Notification Data Present");
navigateToNotifications = true;
}
}
catch (Exception ex)
{
AppLog.Checkpoint("MainActivity Exception getting notification ");
AppLog.CaptureException ("CheckForNotificationData", ex);
}
}
private void CreateNotificationChannel() {
if (Build.VERSION.SdkInt >= BuildVersionCodes.O) {
string name = "Snow emergency notifications";
string description = "This notification channel contains snow emergency notifications.";
NotificationImportance importance = NotificationImportance.Max;
string channelID = AppConstants.NOTIFICATION_CHANNEL_SNOW_EMERGENCIES_ID;
NotificationChannel channel = new NotificationChannel(channelID, name, importance);
channel.Description = description;
NotificationManager notificationManager = (NotificationManager) GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
}
public void FillIoCContainer ()
{
AppLog.Checkpoint("MainActivity Filling IoC");
IoCContainer.Instance.AddInstance<INetworkConnectionMonitor> (new NetworkConnectionMonitor ());
}
}
}