Push notification using Google Cloud Messaging(GCM) in Xamarin.Android

In Brief:
 This Post is regarding the implementation of Remote notification or Push notification using Google cloud messaging.



In my previous post shared my thought about How to build custom alert dialog in Xamarin.AndroidHow to perform Asynchronous CRUD operation with SQLite. How to play youtube video in Xamarin.AndroidHow to implement sliding menu in Xamarin android and in iOS, Best Practice and issues with ListView in Android Xamarin.

In Detail:


Sending the notification message to android device is the one of the best way to make user to more engage with the app. 

As per the Google documentation [https://developers.google.com/cloud-messaging/server] Google Cloud Messaging for Android (GCM) 
is a free service that helps developers send data from servers to their applications on Android devices, 
and upstream messages from the user’s device back to the cloud. 
The GCM service handles all aspects of queueing of messages and
 delivery to the target android application. 
This implementation does require a third party app server to interact with GCM and intern provides the Push notification to the android
 device. Here GCM acts as a intermediator between App server and Device.



GCM Registration and Push notification flow:

1.Android device sends Sender Id to the GCM server for the Registration. 
2.Upon successful registration GCM server returns registration token to device.
3.Device forwards registration token to our app server.
4.Whenever push notification is required, App server sends message to GCM server.
5.GCM server delivers message to respected device.

In Steps:

1. Create project in Google developer portal
.
Step 1.1: Register the project in Google developer console by creating a new project.
Enter the project name and package name.

[As Google updates its developer portal regularly, below mentioned screen may change]



Step 1.2: Select and Enable Google cloud Messaging


Step 1.3: Note generated Server API key and a Sender ID.



Step 1.4: View and edit the project settings in Google developer console.


Select "Permission" from top left sliding menu.

2.Configuration Xamarin.Android project.

Step 2.1: Create new Xamarin android project and Add Google play service GCM package.
 Right click on Packages-> Add Packages... and search Google play service GCM.



Build the project. if you got any error like java.lang.outofmemoryerror, increase the java heap size.
In Xamarin studio,double tap on project(Project option)->Android Build->Advanced->Java heap size->1G

Check for minimum SDK version to support Google play service GCM.



Step 2.2: Enter the project name and package name should be same as entered in Step 1.1)
Open Project Option select Build->Android Application.
 


Step 2.3: Add permission to the manifest file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
 package="com.appliedcodelog.codelog" 
 android:versionCode="1" 
 android:versionName="1.0" 
 android:installLocation="auto">
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="21" />
<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" />
<uses-permission android:name="com.appliedcodelog.codelog.permission.C2D_MESSAGE" />
<permission android:name="com.appliedcodelog.codelog.permission.C2D_MESSAGE" 
 android:protectionLevel="signature" />
<application android:label="CodeLog" android:icon="@drawable/codelogicon">
<receiver android:name="com.google.android.gms.gcm.GcmReceiver" 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.appliedcodelog.codelog" />
</intent-filter>
</receiver>
</application>
</manifest>
Step 2.4: Registration of app with GCM.
 For this, InstanceID API provided by the interface "IntentService" is used to register app in GCM by passing the senderId 
obtained in Step 1.3) and on successful registration GCM returns token.


var instanceID = InstanceID.GetInstance (this);

var token = instanceID.GetToken ("YOUR_SENDER_ID",GoogleCloudMessaging.InstanceIdScope, null);

Step 2.5: Send registration token to the app server.
Pass registration token to the app server for any further process. in this example this method is skipped as we not making use of
 this from app server.



Step 2.6: Subscribes to one or more notification topic channels
,GCM topic messaging allows app server to send message to multiple device. The app server can send the message with payload up to 
2KB to the topic,GCM handles the message routing and delivery to the device.
[https://developers.google.com/cloud-messaging/topic-messaging]

var pubSub = GcmPubSub.GetInstance(this);
   
pubSub.Subscribe(token, "/topics/global", null);
Here our app server sends message to /topics/global, app to receive them as long as app server and app agrees on these names.
"global" indicate that app can receive message on all the topic supported by the app server.
using System;
using Android.App;
using Android.Content; 
using Android.Gms.Gcm;
using Android.Gms.Gcm.Iid;

namespace RemoteNotification
{
 [Service(Exported=false)] //to avoid service instantiation by the system
 public class RegistrationIntentService :IntentService
 {  
  static object locker = new object(); 
  public RegistrationIntentService() : base("RegistrationIntentService") { }

  protected override void OnHandleIntent (Intent intent)
  {
   try
   {  
    //to avoid multiple registration intents occurring simultaneously
    lock (locker)
    {
     //step 3.4
     var instanceID = InstanceID.GetInstance (this);
     var token = instanceID.GetToken (Constants.strSenderId, GoogleCloudMessaging.InstanceIdScope, null); 
     SendRegistrationToAppServer (token);
     Subscribe (token);
    }
   }
   catch  
   {  
    return;
   }  
  }
  //step 3.5
  void SendRegistrationToAppServer (string token)
  {
   // skipped this step.
  }
  //step 3.6
  void Subscribe (string token)
  {
   var pubSub = GcmPubSub.GetInstance(this);
   pubSub.Subscribe(token, "/topics/global", null);
  }
 }
}

Step 2.7: Whenever the App or GCM need to refresh registration token we need to implement interface called "InstanceIdListenerService"
which provides the method "OnTokenRefresh()" by responding to GCM/App request. This is usually requires during app reinstallation or 
for security reasons.
using Android.App;
using Android.Content;
using Android.Gms.Gcm.Iid;

namespace ClientApp
{
 [Service(Exported = false), IntentFilter(new[] { "com.google.android.gms.iid.InstanceID" })]
 class MyInstanceIDListenerService : InstanceIDListenerService
 {
  public override void OnTokenRefresh()
  {
   var intent = new Intent (this, typeof (RegistrationIntentService));
   StartService (intent);
  }
 }
}
Above annotation IntentFilter(new[] { "com.google.android.gms.iid.InstanceID" })] indicates that it can receive GCM registration token.



Step 2.8: At last write the service to catch the message sent by the App server and display locally as notification.
This service make use of the Interface "GcmListenerService" which provides the method called "OnMessageReceived()" by sensing for 
any incoming GCM message.

using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Gms.Gcm; 

namespace RemoteNotification
{
 [Service(Exported=false),IntentFilter(new[]{"com.google.android.c2dm.intent.RECEIVE"})]
 public class MyGcmListenerService :GcmListenerService
 {  
  public override void OnMessageReceived (string from, Bundle data)
  {
   var message = data.GetString ("message"); 
   SendNotification (message); 
  }

  void SendNotification (string message)
  {
   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.CodeLogIcon)
    .SetContentTitle (GetString(Resource.String.app_name))
    .SetContentText (message)
    .SetAutoCancel (true)
    .SetContentIntent (pendingIntent);

   var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
   notificationManager.Notify (0, notificationBuilder.Build());
  }
 }
}
Here intent filter IntentFilter(new[]{"com.google.android.c2dm.intent.RECEIVE"}) indicate that it receives GCM messages.

3.Configure App server (Message sender).

We can have any server side implementation with message sending. Here let us create the console application project to send the push notification. In the next post i will make use of Parse.com for sending push notification.
To create console project.
Step 3.1 : In Xamarin studio, New solution->Other->.Net select Console Project.

Step 3.2: Search and install JSON.Net package to build the json object as we sending json payload with notification message.

Step 3.3: Add System.Net.Http reference
In Xamarin studio,Right click on reference->Edit reference and select System.Net.Http

Step 3.4: Edit the program.cs with the below code
using System;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Text;

namespace NotificationSender
{
 class MainClass
 {
  public const string API_KEY = Constants.strAPIKey;
  public const string MESSAGE = "Hello, coder!";

  public static void Main ( string [] args )
  {
   var jGcmData = new JObject();
   var jData = new JObject();

   jData.Add ("message", MESSAGE);
   jGcmData.Add ("to", "/topics/global");
   jGcmData.Add ("data", jData);

   var url = new Uri ("https://gcm-http.googleapis.com/gcm/send");
   try
   {
    using (var client = new HttpClient())
    {
     client.DefaultRequestHeaders.Accept.Add(
      new MediaTypeWithQualityHeaderValue("application/json"));

     client.DefaultRequestHeaders.TryAddWithoutValidation (
      "Authorization", "key=" + API_KEY);

     Task.WaitAll(client.PostAsync (url,
      new StringContent(jGcmData.ToString(), Encoding.Default, "application/json"))
      .ContinueWith(response =>
      {
       Console.WriteLine(response); 
      } ));
    }
   }
   catch (Exception e)
   {
    Console.WriteLine("Unable to send GCM message:"); 
   }

  }
 }
}

Output Screen:

Final Touch:
This is the quick implementation steps for push notification in xamarin android from the walkthrough provided in the xamarin document.

Reference:https://developer.xamarin.com/guides/cross-platform/application_fundamentals/notifications/android/remote_notifications_in_android/

4 comments:

  1. Hi Suchith Madavu, thanks for the post,
    basically I have done all the things above very similar, everything works fine for a while but after a short amount of time the token generated expires. I create a stack overflow question for more detail in the link below but for now I did not have success.

    http://stackoverflow.com/questions/36737850/how-to-refresh-registration-token-in-gcm-that-expired-after-a-while

    If you can give me any advices it would be great, thanks again.
    Alberto

    ReplyDelete
  2. Hi,
    I can see in your link that you got a solution for the mentioned issue. happy coding, keep visiting. thank you

    ReplyDelete
  3. Hi,
    Thank you for your great post. But I wonder where we can user the token that has been stored at the DB server.

    ReplyDelete
    Replies
    1. Hi Nguyễn,
      Token which is saved in the server is used to send the device specific notification. By using the token one can uniquely identify the device.
      And the token in the client app is used for app re-installation or 
for security reasons.

      Delete