https://developer.xamarin.com/guides/android/data-and-cloud-services/google-messaging/remote-notifications-with-fcm/
This walkthrough provides a step-by-step explanation of how to use Firebase Cloud Messaging to implement remote notifications (also called push notifications) in a Xamarin.Android application. It illustrates how to implement the various classes that are needed for communications with Firebase Cloud Messaging (FCM), provides examples of how to configure the Android Manifest for access to FCM, and demonstrates downstream messaging using the Firebase Console.
In this walkthrough, a basic app called FCMClient will be created to illustrate the essentials of FCM messaging. FCMClientchecks for the presence of Google Play Services, receives registration tokens from FCM, displays remote notifications that you send from the Firebase Console, and subscribes to topic messages:
The following topic areas will be explored:
Background Notifications
Topic Messages
Foreground Notifications
During this walkthrough, you will incrementally add functionality to FCMClient and run it on a device or emulator to understand how it interacts with FCM. You will use logging to witness live app transactions with FCM servers, and you will observe how notifications are generated from FCM messages that you enter into the Firebase Console Notifications GUI.
Before you can proceed with this walkthrough, you must acquire the necessary credentials to use Google's FCM servers; this process is explained in Firebase Cloud Messaging. In particular, you must download the google-services.json file to use with the example code presented in this walkthrough. If you have not yet created a project in the Firebase Console (or if you have not yet downloaded the google-services.json file), see Firebase Cloud Messaging.
To run the example app, you will need an Android test device or emulator that is compatibile with Firebase. Firebase Cloud Messaging supports clients running on Android 2.3 (Gingerbread) or higher, and these devices must also have the the Google Play Store app installed (Google Play Services 9.2.1 or later is required). If you do not yet have the Google Play Store app installed on your device, visit the Google Play web site to download and install it. Alternately, you can use the Android SDK emulator running Android 2.3 or later instead of a test device (you do not have to install the Google Play Store if you are using the Android SDK emulator).
To begin, create a new empty Xamarin.Android project called FCMClient. If you are not familiar with creating Xamarin.Android projects, see Hello, Android. After the new app is created, the next step is to set the package name and install several NuGet packages that will be used for communication with FCM.
In Firebase Cloud Messaging, you specified a package name for the FCM-enabled app. This package name also serves as the application ID that is associated with the API key. Configure the app to use this package name:
Open the properties for the FCMClient project.
In the Android Manifest page, set the package name.
In the following example, the package name is set to com.xamarin.fcmexample
:
While you are updating the Android Manifest, also check to be sure that the Internet
permission is enabled.
Note that the client app will be unable to receive a registration token from FCM if this package name does not exactlymatch the package name that was entered into the Firebase Console.
Because Firebase Cloud Messaging depends on Google Play Services, the Xamarin Google Play Services - Base package must be added to the Xamarin.Android project. You will need version 29.0.0.2 or later.
In Visual Studio, right-click References > Manage NuGet Packages ....
Click the Browse tab and search for Xamarin.GooglePlayServices.Base.
Install this package into the FCMClient project:
If you get an error during installation of the NuGet, close the FCMClient project, open it again, and retry the NuGet installation.
When you install Xamarin.GooglePlayServices.Base, all of the necessary dependencies are also installed. Edit MainActivity.cs and add the following using
statement:
using Android.Gms.Common;
This statement makes the GoogleApiAvailability
class in Xamarin.GooglePlayServices.Base available to FCMClient code.GoogleApiAvailability
is used to check for the presence of Google Play Services.
To receive messages from FCM, the Xamarin Firebase - Messaging package must be added to the app project. Without this package, an Android application cannot receive messages from FCM servers.
In Visual Studio, right-click References > Manage NuGet Packages ....
Check Include prerelease and search for Xamarin.Firebase.Messaging.
Install this package into the FCMClient project:
When you install Xamarin.Firebase.Messaging, all of the necessary dependencies are also installed.
Next, edit MainActivity.cs and add the following using
statements:
using Firebase.Messaging;
using Firebase.Iid;
using Android.Util;
The first two statements make types in the Xamarin.Firebase.Messaging NuGet package available to FCMClient code. Android.Util adds logging functionality that will be used to observe transactions with FMS.
The next step is to add the google-services.json file to the root directory of your project:
Copy google-services.json to the project folder.
Add google-services.json to the app project (click Show All Files in the Solution Explorer, right click google-services.json, then select Include in Project).
Select google-services.json in the Solution Explorer window.
In the Properties pane, set the Build Action to GoogleServicesJson (if the GoogleServicesJson build action is not shown, save and close the Solution, then reopen it):
When google-services.json is added to the project (and the GoogleServicesJson build action is set), the build process extracts the client ID and API key and then adds these credentials to the merged/generated AndroidManifest.xml that resides at obj/Debug/android/AndroidManifest.xml. This merge process automatically adds any permissions and other FCM elements that are needed for connection to FCM servers.
An initial layout for the app's UI will be created first. Edit Resources/layout/Main.axml and replace its contents with the following XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:text=" "
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/msgText"
android:textAppearance="?android:attr/textAppearanceMedium"
android:padding="10dp" />
</LinearLayout>
This TextView
will be used to display messages that indicate whether Google Play Services is installed. Save the changes to Main.axml. Edit MainActivity.cs and add the following instance variable declaration to the MainActivity
class:
TextView msgText;
Google recommends that Android apps check for the presence of the Google Play Services APK before accessing Google Play Services features (for more information, see Check for Google Play services). In the following example, the OnCreate
method will verify that Google Play Services is available before the app attempts to use FCM services. Add the following method to the MainActivity
class:
public bool IsPlayServicesAvailable ()
{
int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable (this);
if (resultCode != ConnectionResult.Success)
{
if (GoogleApiAvailability.Instance.IsUserResolvableError (resultCode))
msgText.Text = GoogleApiAvailability.Instance.GetErrorString (resultCode);
else
{
msgText.Text = "This device is not supported";
Finish ();
}
return false;
}
else
{
msgText.Text = "Google Play Services is available.";
return true;
}
}
This code checks the device to see if the Google Play Services APK is installed. If it is not installed, a message is displayed in the TextBox
that instructs the user to download an APK from the Google Play Store (or to enable it in the device's system settings).
Replace the OnCreate
method with the following code:
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView (Resource.Layout.Main);
msgText = FindViewById<TextView> (Resource.Id.msgText);
IsPlayServicesAvailable ();
}
IsPlayServicesAvailable
is called at the end of OnCreate
so that the Google Play Services check runs each time the app starts. If your app has an OnResume
method, it should call IsPlayServicesAvailable
from OnResume
as well. Completely rebuild and run the app. If all is configured properly, you should see a screen that looks like the following screenshot:
If you don't get this result, verify that the Google Play Services APK is installed on your device (for more information, seeSetting Up Google Play Services). Also verify that you have added the Xamarin.Google.Play.Services.Base package to your FCMClient project as explained earlier.
The next step is to add a service that extends FirebaseInstanceIdService
to handle the creation, rotation, and updating of Firebase registration tokens. (For more about registration tokens, see Registration with FCM.) The FirebaseInstanceIdService
service is required for FCM to be able to send messages to the device. When the FirebaseInstanceIdService
service is added to the client app, the app will automatically receive FCM messages and display them as notifications whenever the app is backgrounded.
Edit AndroidManifest.xml and insert the following <receiver>
elements into the <application>
section:
<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>
This XML does the following:
Declares a FirebaseInstanceIdReceiver
implementation that provides a unique identifier for each app instance. This receiver also authenticates and authorizes actions.
Declares an internal FirebaseInstanceIdInternalReceiver
implementation that is used to start services securely.
The FirebaseInstanceIdReceiver
is a WakefulBroadcastReceiver
that receives FirebaseInstanceId
and FirebaseMessaging
events and delivers them to the class that you derive from FirebaseInstanceIdService
.
The work of registering the application with FCM is handled by the custom FirebaseInstanceIdService
service that you provide. FirebaseInstanceIdService
performs the following steps:
Uses the Instance ID API to generate security tokens that authorize the client app to access FCM and the app server. In return, the app gets back a registration token from FCM.
Forwards the registration token to the app server if the app server requires it.
Add a new file called MyFirebaseIIDService.cs and replace its template code with the following:
using System;
using Android.App;
using Firebase.Iid;
using Android.Util;
namespace FCMClient
{
[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class MyFirebaseIIDService : FirebaseInstanceIdService
{
const string TAG = "MyFirebaseIIDService";
public override void OnTokenRefresh()
{
var refreshedToken = FirebaseInstanceId.Instance.Token;
Log.Debug(TAG, "Refreshed token: " + refreshedToken);
SendRegistrationToServer(refreshedToken);
}
void SendRegistrationToServer(string token)
{
// Add custom implementation, as needed.
}
}
}
This service implements an OnTokenRefresh
method that is invoked when the registration token is initially created or changed. When OnTokenRefresh
runs, it retrieves the latest token from the FirebaseInstanceId.Instance.Token
property (which is updated asynchronously by FCM). In this example, the refreshed token is logged so that it can be viewed in the output window:
var refreshedToken = FirebaseInstanceId.Instance.Token;
Log.Debug(TAG, "Refreshed token: " + refreshedToken);
OnTokenRefresh
is invoked infrequently: it is used to update the token under the following circumstances:
When the app is installed or uninstalled.
When the user deletes app data.
When the app erases the Instance ID.
When the security of the token has been compromised.
According to Google's Instance ID documentation, the FCM Instance ID service will request that the app refresh its token periodically (typically, every 6 months).
OnTokenRefresh
also calls SendRegistrationToAppServer
to associate the user's registration token with the server-side account (if any) that is maintained by the application:
void SendRegistrationToAppServer (string token)
{
// Add custom implementation here as needed.
}
Because this implementation depends on the design of the app server, an empty method body is provided in this example. If your app server requires FCM registration information, modify SendRegistrationToAppServer
to associate the user's FCM instance ID token with any server-side account maintained by your app. (Note that the token is opaque to the client app.)
When a token is sent to the app server, SendRegistrationToAppServer
should maintain a boolean to indicate whether the token has been sent to the server. If this boolean is false, SendRegistrationToAppServer
sends the token to the app server – otherwise, the token was already sent to the app server in a previous call. In some cases (such as this FCMClient
example), the app server does not need the token; therefore, this method is not required for this example.
Now that the receiver services are in place, client app code can be written to take advantage of these services. In the following sections, a button is added to the UI to log the registration token (also called the Instance ID token), and more code is added to MainActivity
to view Intent
information when the app is launched from a notification:
The code added in this step is intended only for demonstration purposes – a production client app would have no need to log registration tokens. Edit Resources/layout/Main.axml and add the following Button
declaration immediately after the TextView
element:
<Button
android:id="@+id/logTokenButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Log Token" />
Edit MainActivity.cs and add the following member declaration to the MainActivity
class:
const string TAG = "MainActivity";
Add the following code to the end of the OnCreate
method:
var logTokenButton = FindViewById<Button>(Resource.Id.logTokenButton);
logTokenButton.Click += delegate {
Log.Debug(TAG, "InstanceID token: " + FirebaseInstanceId.Instance.Token);
};
This code logs the current token to the output window when the Log Token button is tapped.
When the user taps a notification issued from FCMClient, any data accompanying that notification message is made available in Intent
extras. Edit MainActivity.cs and add the following code to the top of the OnCreate
method (before the call to IsPlayServicesAvailable
):
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
var value = Intent.Extras.GetString(key);
Log.Debug(TAG, "Key: {0} Value: {1}", key, value);
}
}
The app's launcher Intent
is fired when the user taps its notification message, so this code will log any accompanying data in the Intent
to the output window. If a different Intent
must be fired, the click_action
field of the notification message must be set to that Intent
(the launcher Intent
is used when no click_action
is specified).
Build and run the FCMClient app. The Log Token button is displayed:
Tap the Log Token button. A message like the following should be displayed in the IDE output window:
The long string labeled with token is the instance ID token that you will paste into the Firebase Console – select and copy this string to the clipboard. If you do not see an instance ID token, add the following line to the top of the OnCreate
method to verify that google-services.json was parsed correctly:
Log.Debug(TAG, "google app id: " + Resource.String.google_app_id);
The google_app_id
value logged to the output window should match the mobilesdk_app_id
value recorded in google-services.json.
Sign into the Firebase Console, select your project, click Notifications, and click SEND YOUR FIRST MESSAGE:
On the Compose message page, enter the message text and select Single device. Copy the instance ID token from the IDE output window and paste it into the FCM registration token field of the Firebase Console:
On the Android device (or emulator), background the app by tapping the Android Overview button and touching the home screen. When the device is ready, click SEND MESSAGE in the Firebase Console:
When the Review message dialog is displayed, click SEND. The notification icon should appear in the notification area of the device (or emulator):
Open the notification icon to view the message. The notification message should be exactly what was typed into the Message text field of the Firebase Console:
Tap the notification icon to return to the FCMClient app. The Intent
extras sent to FCMClient are listed in the IDE output window:
In this example, the from key is set to the Firebase project number of the app (in this example, 41590732), and the collapse_key is set to its package name (com.xamarin.fcmexample). If you do not receive a message, try deleting the FCMClient app on the device (or emulator) and repeat the above steps.
Note: if you force-close the app, FCM will stop delivering notifications. Android prevents background service broadcasts from inadvertently or unnecessarily launching components of stopped applications. (For more information about this behavior, see Launch controls on stopped applications.) For this reason, it is necessary to manually uninstall the app each time you run it and stop it from a debug session – this forces FCM to generate a new token so that messages will continue to be received.
In the previous example, the notification icon is set to the application icon. The following XML configures a custom default icon for notifications. Android displays this custom default icon for all notification messages where the notification icon is not explicitly set.
To add a custom default notification icon, add your icon to the Resources/drawable directory, edit AndroidManifest.xml, and insert the following <meta-data>
element into the <application>
section:
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_ic_notification" />
In this example, the notification icon that resides at Resources/drawable/ic_stat_ic_notification.png will be used as the custom default notification icon. If a custom default icon is not configured in AndroidManifest.xml and no icon is set in the notification payload, Android uses the application icon as the notification icon (as seen in the notification icon screenshot above).
The code written thus far handles registration tokens and adds remote notification functionality to the app. The next example adds code that listens for topic messages and forwards them to the user as remote notifications. Topic messages are FCM messages that are sent to one or more devices that subscribe to a particular topic. For more information about topic messages, see Topic Messaging.
Edit Resources/layout/Main.axml and add the following Button
declaration immediately after the previous Button
element:
<Button
android:id="@+id/subscribeButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:text="Subscribe to Notifications" />
This XML adds a Subscribe to Notification button to the layout. Edit MainActivity.cs and add the following code to the end of the OnCreate
method:
var subscribeButton = FindViewById<Button>(Resource.Id.subscribeButton);
subscribeButton.Click += delegate {
FirebaseMessaging.Instance.SubscribeToTopic("news");
Log.Debug(TAG, "Subscribed to remote notifications");
};
This code locates the Subscribe to Notification button in the layout and assigns its click handler to code that callsFirebaseMessaging.Instance.SubscribeToTopic
, passing in the subscribed topic, news. When the user taps the Subscribebutton, the app subscribes to the news topic. In the following section, a news topic message will be sent from the Firebase Console Notifications GUI.
Uninstall the app, rebuild it, and run it again. Click the Subscribe to Notifications button:
If the app has subscribed successfully, you should see topic sync succeeded in the IDE output window:
Use the following steps to send a topic message:
In the Firebase Console, click NEW MESSAGE.
On the Compose message page, enter the message text and select Topic.
In the Topic pull-down menu, select the built-in topic, news:
On the Android device (or emulator), background the app by tapping the Android Overview button and touching the home screen.
When the device is ready, click SEND MESSAGE in the Firebase Console.
Check the IDE output window to see /topics/news in the log output:
When this message is seen in the output window, the notification icon should also appear in the notification area on the Android device. Open the notification icon to view the topic message:
If you do not receive a message, try deleting the FCMClient app on the device (or emulator) and repeat the above steps.
To receive notifications in foregrounded apps, you must implement FirebaseMessagingService
. This service is also required for receiving data payloads and for sending upstream messages. The following examples illustrate how to implement a service that extends FirebaseMessagingService
– the resulting app will be able to handle remote notifications while it is running in the foreground.
The FirebaseMessagingService
service includes an OnMessageReceived
method that you write to handle incoming messages. Note that OnMessageReceived
is invoked for notification messages only when the app is running in the foreground. When the app is running in the background, an automatically generated notification is displayed (as demonstrated earlier in this walkthrough).
Add a new file called MyFirebaseMessagingService.cs and replace its template code with the following:
using System;
using Android.App;
using Android.Content;
using Android.Media;
using Android.Util;
using Firebase.Messaging;
namespace FCMClient
{
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class MyFirebaseMessagingService : FirebaseMessagingService
{
const string TAG = "MyFirebaseMsgService";
public override void OnMessageReceived(RemoteMessage message)
{
Log.Debug(TAG, "From: " + message.From);
Log.Debug(TAG, "Notification Message Body: " + message.GetNotification().Body);
}
}
}
Note that the MESSAGING_EVENT
intent filter must be declared so that new FCM messages are directed to MyFirebaseMessagingService
:
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
When the client app receives a message from FCM, OnMessageReceived
extracts the message content from the passed-in RemoteMessage
object by calling its GetNotification
method. Next, it logs the message content so that it can be viewed in the IDE output window:
Log.Debug(TAG, "From: " + message.From);
Log.Debug(TAG, "Notification Message Body: " + message.GetNotification().Body);
Note: If you set breakpoints in
FirebaseMessagingService
, your debugging session may or may not hit these breakpoints because of how FCM delivers messages.
Uninstall the app, rebuild it, run it again, and follow these steps to send another message:
In the Firebase Console, click NEW MESSAGE.
On the Compose message page, enter the message text and select Single device.
Copy the token string from the IDE output window and paste it into the FCM registration token field of the Firebase Console as before.
Ensure that the app is running in the foreground, then click SEND MESSAGE in the Firebase Console:
When the Review message dialog is displayed, click SEND.
The incoming message is logged to the IDE output window:
In this remaining example, the incoming FCM message will be converted into a local notification that is launched while the app is running in the foreground. Edit MyFirebaseMessageService.cs and add the following using
statement:
using FCMClient;
Add the following method to MyFirebaseMessagingService
:
void SendNotification(string messageBody)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Notification.Builder(this)
.SetSmallIcon(Resource.Drawable.ic_stat_ic_notification)
.SetContentTitle("FCM Message")
.SetContentText(messageBody)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManager.FromContext(this);
notificationManager.Notify(0, notificationBuilder.Build());
}
To distinguish this notification from background notifications, this code marks notifications with an icon that differs from the applicatiion icon. Add ic_stat_ic_notification.png to Resources/drawable and include it in the FCMClient project.
The SendNotification
method uses Notification.Builder
to create the notification, and NotificationManager
is used to launch the notification. The notification holds a PendingIntent
that will allow the user to open the app and view the contents of the string passed into messageBody
. Depending on the versions of Android that you intend to target with your app, you may want to use NotificationCompat.Builder
instead. For more information about Notification.Builder
, seeLocal Notifications.
Call the SendNotification
method at end of the OnMessageReceived
method:
SendNotification (message.GetNotification().Body);
As a result of these changes, SendNotification
will run whenever a notification is received while the app is in the foreground, and the notification will appear in the notification area. If the app is backgrounded, SendNotification
will not run and, instead, a background notification (illustrated earlier in this walkthrough) will be launched.
Uninstall the app, rebuild it, run it again, then use the following steps to send the last message:
In the Firebase Console, click NEW MESSAGE.
On the Compose message page, enter the message text and select Single device.
Copy the token string from the IDE output window and paste it into the FCM registration token field of the Firebase Console as before.
Ensure that the app is running in the foreground, then click SEND MESSAGE in the Firebase Console:
This time, the message that was logged in the output window is also packaged in a new notification – the notification icon appears in the notification tray while the app is running in the foreground:
When you open the notification, you should see the last message that was sent from the Firebase Console Notifications GUI:
The following describe issues and workarounds that may arise when using Firebase Cloud Messaging with Xamarin.Android.
In some cases, you may see this error message:
Java.Lang.IllegalStateException: Default FirebaseApp is not initialized in this process
Make sure to call FirebaseApp.initializeApp(Context) first.
This is a known problem that you can work around by cleaning the solution and rebuilding the project (Build > Clean Solution, Build > Rebuild Solution). For more information, see this forum discussion.
This walkthrough detailed the steps for implementing Firebase Cloud Messaging remote notifications in a Xamarin.Android application. It described how to install the required packages needed for FCM communications, and it explained how to configure the Android Manifest for access to FCM servers. It provided example code that illustrates how to check for the presence of Google Play Services. It demonstrated how to implement an instance ID listener service that negotiates with FCM for a registration token, and it explained how this code creates background notifications while the app is backgrounded. It explained how to subscribe to topic messages, and it provided an example implementation of a message listener service that is used to receive and display remote notifications while the app is running in the foreground.