2
votes

During my migration from Native Android and iOS to Xamarin.Forms, I had decided to use AppCenter Push for my notification, since it is free and easy to use (of course I spent a good amount of time to make it works since it's relatively new thing with less guidance online). You can find my original sharing at How to implement AppCenter Push API?.

I was happy with it until Microsoft announced that it is retiring AppCenter Push (https://devblogs.microsoft.com/appcenter/app-center-mbaas-retirement/) and encouraged user to move over to Azure (which is a paid service). I decided to move back to use the native FCM and APN for my push notification.

The problem is there is no direct tutorials on how to do the entire thing. There are problems & solutions here and there such as iOS .P8 only works on HTTP/2 while my project is running on .Net Framework which is not supported. Only .Net Core could HTTP/2 protocol.

2
Whilst it is fine to answer your own questions, consider re-wording your question so that it doesn't read like a blog. Apart from the title, I'm not sure you are actually asking a question here.MickyD

2 Answers

2
votes

My current project runs on ASP.NET C# as the backend, which send notification to Xamarin.Android and Xamarin.iOS using Xamarin.Forms. If you're like me, please find my answer below, I am sharing the fully working C# backend and Xamarin.Forms solution below. So that more user can benefit from FREE service instead of being pushed to Azure paid service.

PART 1 C# Backend - The C# ASP.NET backend. It will be split to 2 parts, for FCM and APNs.

1.1) Firebase (FCM)

  1. To setup FCM, you'll need to register an account. There are tonnes of guideline online, this is one of the good one https://xmonkeys360.com/2019/12/08/xamarin-forms-fcm-setup-configuration-part-i/. Remember to get the Server Key and download the google-services.json file to your Xamarin.Android project. Right-click and set the build action to "GoogleServiceJson" (Where can i add google-services.json in xamarin app).

  2. Below is my Firebase

    using Newtonsoft.Json.Linq;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Web.Script.Serialization;
    
    namespace PushNotificationLibrary
    {
        public class FirebaseCloudMessagingPush
        {
            private const string WEB_ADDRESS = "https://fcm.googleapis.com/fcm/send";
    
            private const string SENDER_ID = "YOUR SENDER ID";
            private const string SERVER_KEY = "YOUR SERVER KEY";
    
            public string SendNotification(string deviceToken, string title, string message, string priority = "high", int badge = 0, List<Tuple<string, string>> parameters = null)
            {
                var result = "-1";
                var httpWebRequest = (HttpWebRequest)WebRequest.Create(WEB_ADDRESS);
    
                parameters = parameters ?? new List<Tuple<string, string>>();
    
                httpWebRequest.ContentType = "application/json";
                httpWebRequest.Headers.Add(string.Format("Authorization: key={0}", SERVER_KEY));
                httpWebRequest.Headers.Add(string.Format("Sender: id={0}", SENDER_ID));
                httpWebRequest.Method = "POST";
    
                if (title.Length > 100)
                    title = title.Substring(0, 95) + "...";
    
                //Message cannot exceed 100
                if (message.Length > 100)
                    message = message.Substring(0, 95) + "...";
    
                JObject jObject = new JObject();
                jObject.Add("to", deviceToken);
                jObject.Add("priority", priority);
                jObject.Add("content_available", true);
    
                JObject jObjNotification = new JObject();
                jObjNotification.Add("body", message);
                jObjNotification.Add("title", title);            
    
                jObject.Add("notification", jObjNotification);
    
                JObject jObjData = new JObject();
    
                jObjData.Add("badge", badge);
                jObjData.Add("body", message);
                jObjData.Add("title", title);
    
                foreach (Tuple<string, string> parameter in parameters)
                {
                    jObjData.Add(parameter.Item1, parameter.Item2);
                }
    
                jObject.Add("data", jObjData);
    
                var serializer = new JavaScriptSerializer();
                using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
                {
                    string json = jObject.ToString();
                    streamWriter.Write(json);
                    streamWriter.Flush();
                }
    
                var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
                {
                    result = streamReader.ReadToEnd();
                }
    
                return result;
            }
        }
    }
    

1.2) iOS (APNs)

  1. For APNs, there are 2 ways of doing things. One is using .P12 conventional way. Another way is to use Apple Auth Keys using .P8. I prefer to use .P8 because .P12 cert expiry every year and need to update annually. The problem with using .P8 is that, it's using HTTP/2 which is not supported in .Net Framework but thankfully, I managed to overcome that. Please refer to How to implement apple token based push notifications (using p8 file) in C#?, find my Answer on that post on how to implement the whole thing for .Net Framework. *IF you're already using .Net Core, the answer will be similar to mine, but you do not have to use the custom WinHTTPHandler. Just the normal HTTPClient will do.

PART 2 Xamarin.Forms - Next, you'll have to support the notification in your Xamarin.Forms project.

  1. For this part, it's not very difficult at all. All you have to do is refer to https://github.com/CrossGeeks/PushNotificationPlugin, download the Nuget and follow the instructions in the link to setup for your Xamarin projects (Forms, Android, iOS).

  2. The only thing that I wish to highlight is how & where to get your device token. Initially I was trying to get the device token at the code below (OnTokenRefresh). But you'll soon to notice that this code doesn't always get called, I suspect it will only get called once the token is refreshed, and not every time when you debug. In order to obtain your Device Token every time, just call CrossPushNotification.Current.Token anywhere in your project. Register that device token to your server backend. And use the device token to send the notification using my code at PART 1 above.

    CrossPushNotification.Current.OnTokenRefresh += (s,p) =>
    {
        System.Diagnostics.Debug.WriteLine($"TOKEN : {p.Token}");
    };
    

That's it! It's pretty easy, but I spent weeks to try and error before piecing them together. Hope that it's able to save precious time from others.

0
votes

Server:

Try FirebaseAdmin for server side. Very straighforward with this package.

https://github.com/firebase/firebase-admin-dotnet

Follow these setup instructions:

https://firebase.google.com/docs/admin/setup#c

For the Xamarin app:

I decided I didn't want to use the CrossGeeks plugin and it was pretty straightforward.

For Android:

Install the relevant Xamarin.Firebase packages and create your own Firebase Messaging Class in Android project inheriting the package FirebaseMessagingService.

[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
class PushNotificationFirebaseMessagingService : FirebaseMessagingService
{
    private static string foregroundChannelId = "9001";

    public override void OnNewToken(string refreshedToken)
    {
        base.OnNewToken(refreshedToken);

        SendRegistrationToServer(refreshedToken);
    }

    private void SendRegistrationToServer(string token)
    {
        //Your code here to register device token on server
    }

    public override void OnMessageReceived(RemoteMessage message)
    {
        SendNotification(message);

        base.OnMessageReceived(message);
    }

    private void SendNotification(RemoteMessage message)
    {
        try
        {
            var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;

            var notificationChannel = new NotificationChannel(foregroundChannelId, "messaging_channel", NotificationImportance.High);

            var audioAttributes = new AudioAttributes.Builder()
                .SetContentType(AudioContentType.Sonification)
                .SetUsage(AudioUsageKind.Notification).Build();

            var notificationUri = RingtoneManager.GetDefaultUri(RingtoneType.Notification);

            notificationChannel.EnableLights(true);
            notificationChannel.EnableVibration(true);
            notificationChannel.SetSound(notificationUri, audioAttributes);


            notificationManager.CreateNotificationChannel(notificationChannel);

            var remoteNotification = message.GetNotification();

            var builder = new Notification.Builder(this, foregroundChannelId)
                .SetContentTitle(remoteNotification.Title)
                .SetContentText(remoteNotification.Body)
                .SetSmallIcon(Resource.Mipmap.icon);

            var notification = builder.Build();

            notificationManager.Notify(0, notification);
        }
        catch (Exception ex)
        {

        }
    }
}

Add the following to the AndroidManifest.xml in the Application tag.

<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>