Hello friends, what will a good mobile app be like without push notifications nowadays? No matter if your app is a chat app, a ride-sharing app, or a simple news app, you surely want to keep your users engaged, and let them come back to your app very often. Push notifications have become indispensable if you want to keep your audience engaged and grow your user base.
Firebase cloud messaging is a widely used solution to provide push notifications to your apps and its SDK is well documented especially if you are a JavaScript, android native, iOS dev etc. Luckily, we have a firebase admin SDK for dotnet and the Xamarin Firebase bindings available for us dotnet developers. These tools are awesome, but some features offered by firebase are not available in it like; Device messaging. And, implementing certain functionalities might be tricky, especially if you are new to firebase cloud messaging. So, in this article we will go through some tips and tricks while implementing Firebase push notifications for dotnet apps (Asp.net core and Xamarin).
While implementing Firebase push notifications for dotnet, this post assumes you are a dotnet developer, and you are using the Firebase Admin SDK for dotnet. We will also assume you have a basic knowledge of how firebase push notifications function. You can find the source code for this article here. To find a basic guide to implement firebase notifications in your Xamarin.Android app, follow this link and for Xamarin.iOS, follow this link for a detailed documentation.
Here is what we will be doing today;
- Sending notifications to All the devices belonging to one user
- Sending notifications to several users
- How to send silent notifications
- Receiving notifications when your app is killed or closed
- Displaying image attachments in your notifications
- How to handle notification taps and perform appropriate actions
- Detect when notifications are dismissed by users
- What to do if firebase push notifications do not reach your Xamarin Mobile apps
Sending notifications to All the devices belonging to one user (Device group Messaging)
When implementing push notifications for your mobile app, you have to take into consideration that users can have multiple devices. These devices will then be assigned device tokens. These tokens can easily be managed using device group messaging. Device groups help developers manage their users’ device tokens and several operations could be performed to manage device groups.
Note; I reached out to the guys maintaining the Firebase admin SDK for dotnet, to know why Device group messaging was not yet implemented in the dotnet admin SDK. As of the time I’m writing this article, they mentioned that no implementation for device group messaging in the admin SDKs was made yet because; The firebase team is working on a better approach to manage device group and topic messaging. This approach will leverage topic messaging in a more appropriate way and resolve some limitations which we have in topic messaging. You can see some of the limitations mentioned here. Once implemented in the dotnet SDK, this will surely make firebase push notifications for dotnet easier to implement. Not only in the dotnet world, but Firebase push notifications everywhere in general.
Implementing Device group messaging
Since no implementation for device group messaging was made in the firebase dotnet admin SDK, we have to do it via HTTP calls. You can see the operations available for device groups here. I wrote a small library that will help you perform these operations, you can find it here. This project also contains a test API which you can use to test the various notifications scenarios.
First, to send HTTP calls to the firebase device group API, obviously after creating your firebase app, you should go to Firebase Console > Settings > Cloud Messaging Tab and get your Project Id and Server API key. These will let us authenticate to the API.
Basically, the operations made on the firebase API consists of POST and GET requests. so, we will make C# http requests, while taking certain things into consideration.
Note: GET requests to the firebase API should contain an empty body of type JSON. (Not sure why this was made this way, but as of when I wrote this, making a GET request with no body won’t work).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var urlEncoded = requestParams.Where(kvp => !string.IsNullOrEmpty(kvp.Value) && kvp.Value != "-1") .Select(kvp => $"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}"); url = $"{url}?{string.Join("&", urlEncoded)}"; HttpRequestMessage requestMessage = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(url), Headers = { { "project_id", _firebaseConfig.SenderId }, }, ///NB: This get request must have this empty body, else an error is fired, stating it is not a json request Content = new StringContent("", Encoding.UTF8, "application/json") }; |
Note: when authenticating to the API, you need to pass the authorization header without validation. Else, validation will fail systematically when making your requests. As follows:
1 2 | httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"key={_firebaseConfig.APIKey}"); |
Then, with the library, you can perform these basic operations on Firebase device groups.
- Create a Firebase device group
1 | Task<DeviceGroupResponse> CreateDeviceGroup(string userId, string token) |
- Add a token to the firebase device group
1 2 | Task<DeviceGroupResponse> AddTokenToDeviceGroup(string userId, string token, string notificationKey) |
- Get the Firebase device group’s notification key
1 | Task<DeviceGroupResponse> GetDeviceGroupNotificationKey(string userId) |
- Send notifications to the device group
1 | Task<SendDeviceMessageResponse> SendNotificationToDevices(DeviceMessage message) |
Device Group Error responses
While performing the above operations, you might encounter a few errors if you misuse the API. Firebase will return error codes if for example; you create device groups that exist or try to get device groups that do not exist. The above library contains an abstraction of the errors, which facilitates the process of handling them in C# code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class DeviceGroupResponse { [JsonProperty("notification_key")] public string NotificationKey { get; set; } [JsonProperty("error")] public string Error { get; set; } public bool IsSuccess => string.IsNullOrWhiteSpace(Error); public ErrorType ErrorType { get => Error switch { "notification_key already exists" => ErrorType.DeviceGroupAlreadyExists, "notification_key not found" => ErrorType.NotificationKeyNotFound, _ => ErrorType.None }; } |
With the above, you can easily send notifications to users with multiple devices and manage these devices easily.
Note that; to delete a device group, you have to remove every firebase token contained in that device group and it will be deleted.
Sending firebase push notifications to multiple users or a User
Obviously, firebase push notifications allow you to target several users with your notifications. You can do it either by using a “Multicast message” or “Topic messaging”. The latter is the recommended way of sending notifications to multiple users. The firebase admin SDK for dotnet already contains several methods for working with topics. For anyone willing to implement firebase push notifications for dotnet, here is a good documentation for topic messaging in C#.
To send a push notification to one user, you need that user’s firebase token. And you simply use it while constructing the message to be sent.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | await FirebaseMessaging.DefaultInstance.SendAsync(new Message { Token = token, Notification = new Notification { Body = body, Title = title, ImageUrl = imageUrl }, Data = data, Apns = new ApnsConfig { Aps = new Aps { ContentAvailable = true, MutableContent = true, } }, }); |
Troubleshooting Firebase Notifications in Xamarin Apps
While targeting mobile devices with Firebase push notifications, you might find it difficult at first to implement certain scenarios. There are some specific things that can drag your legs, and take you hours to implement. On the internet, you will find thousands of solutions, but some might be deprecated, and others might not be the bests. I’ve gone through this, and here are the lessons I learnt. This might save you time, and if you have a better way of accomplishing this, please let me know in the comments section.
We will not talk about setting up Firebase push notifications on Xamarin.Android or Xamarin.iOS apps, because it is well documented already. We will only go through difficulties beyond setting up your apps.
How do I receive push notifications when my app is closed ?
This can be a little bit tricky, and it varies depending on the platform.
Xamarin Android
On android, normal notifications are only received when your app is in the foreground. To make your app listen to background notifications, you need to send “data notifications” from your backend. This is simple, it implies adding a key-value pair in the data attribute of the firebase message.
Xamarin iOS
On iOS, you will need to set two properties of the APS notifications to true. The Firebase cloud messaging admin SDK for dotnet will then translate it to appropriate configurations and iOS will receive your notifications in the background.
Below, is an example of a silent notification (Android and iOS combined)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var message = new Message { Notification = new Notification { Body = body, Title = title, ImageUrl = imageUrl }, Data = data, Apns = new ApnsConfig { Aps = new Aps { ContentAvailable = true, MutableContent = true, } }, }; |
How do I Display Large Images in My Notifications
Plain notifications are easy to make, since they consist only of the basic portions of a notification (Title, Description and app Icon). What if we need to display notifications with attachments like Images or Gifs ?. Each platform has its way of handling this.
Xamarin iOS
On iOS, to display custom notifications with an attachment such as a large image here are the steps you should follow;
- You have to implement what is known as a Notification Service Extension. This will let you intercept your notification and modify it before it is displayed by iOS. This documentation will guide you through setting up service extensions. After setting this up, run your app and when you receive a notification, its title should be modified as stated in the doc.
NOTE: For this service extension to work, make sure you did these correctly
- The bundle id of your notification service extension should start with the bundle id of your main app, then you append the notification extension’s project name and add “service extension” to it. That is;
1 2 3 | Your App Bundle ID: <code>com.companyname.appname.myapp Your Notification Service Extension's Bundle Id: <code>com.companyname.appname.test.xxxxserviceextension Where "XXX" can be any name you give to your project. |
- If the service extension still didn’t work, Check your notification service’s deployment target, and let it be the same as that of the main iOS project
- The next step is to get your image URL from the firebase notification. Here is how you do it:
1 2 3 4 5 | var userInfo = BestAttemptContent.UserInfo; var firebaseCloudMessagingOptions = ((NSDictionary)userInfo["fcm_options"]); if (firebaseCloudMessagingOptions != null) imageUrl = firebaseCloudMessagingOptions["image"].ToString(); |
- Then we download the image in a temp folder and load it into the notification, still inside the method “DidReceiveNotificationRequest” as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //Load the image in a UIImage var uiImage = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(imageUrl))); //Then we create an attachment with this method UNNotificationAttachment CreateNotificationAttachment(UIImage image, string imageName, out NSError error) { var fileManager = NSFileManager.DefaultManager; var tempSubFolder = NSProcessInfo.ProcessInfo.GloballyUniqueString; var tempSubFolderUrl = new NSUrl(System.IO.Path.GetTempPath(), true); //Create image dir fileManager.CreateDirectory(tempSubFolder, true, null); var imageFileIdentifier = $"{imageName}.png"; var fileUrl = tempSubFolderUrl.Append(imageFileIdentifier, false); //Continue with this: https://stackoverflow.com/a/39103096/7442601 //Save image var imageData = image.AsPNG(); imageData.Save(fileUrl, true); var imageAttachement = UNNotificationAttachment.FromIdentifier(imageFileIdentifier, fileUrl, new NSDictionary(), out error); return imageAttachement; } //We later add the attachment to our notification BestAttemptContent.Attachments = new UNNotificationAttachment[] { attachment }; //That is all |
Xamarin Android
On Android, it is easier to add an image attachment to your notification. You load the image as a bitmap image from its URL, then add it to your notification. You also add the large image style. Here is the code to accomplish this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var url = new URL(imageUrl); var connection = (HttpURLConnection)url.OpenConnection(); connection.DoInput = true; connection.Connect(); var input = connection.InputStream; var bitmap = BitmapFactory.DecodeStream(input); var style = new NotificationCompat.BigPictureStyle() .BigPicture(bitmap) .SetSummaryText(message); connection.Dispose(); builder.SetStyle(style); //Then use the builder to build your notification normally... |
How to handle notification clicks then perform appropriate actions
Sending push notifications is good. But, what is better is to control what action your app performs after the user clicked on the notification. This is quite easy on iOS, but can be tricky on Android.
Xamarin Android
On android, messages sent to firebase cloud messaging can contain action intents. To precise an action intent for a notification, using FCM Admin SDK for dotnet, do the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | new Message { ..., ..., Android = new AndroidConfig { Notification = new AndroidNotification { ..., ClickAction = "Show_Main_Screen", } } }; |
Once the notification is intercepted by the android OS if it contains an intent, Android checks which Activity, Service… is responsible for handling this intent. If it does not find anyone, android will send this error message silently “Notification pending intent canceled“, and not call your app if it is closed. This behavior is kind of misleading, and it wasted me hours… Online, I could see several other developers complained about this too. To handle your intents, you need to add an intent filter for your intent, in your activity and specifying your activity as the default category.
For example, if I want to call my MainActivity when my notification’s click_action property has the value Show_Main_Screen, I’ll have to decorate my activity as follows:
1 2 3 4 5 6 7 8 9 | [IntentFilter(new [] { "Show_Main_Screen" }, Categories=new[] { global::Android.Content.Intent.CategoryDefault })] public class MainActivity : |
The above goes for any activity and intent.
Once this is done, android will call the appropriate activity when the notification is received. No matter if the app is closed, the screen is locked or the app was just killed.
NOTE: To get your notification data using this approach, just go through your intent extras, in the “OnResume” method. You will find your data key-value pairs listed in the intent’s extras.
Xamarin iOS
On iOS, it is easier. All you have to do in your app is precise in your code what delegate should be used as the UNUserNotificationCenterDelegate. As follows:
1 | UNUserNotificationCenter.Current.Delegate = new NotificationDelegate(); |
And, implement the appropriate methods. Below, you can find the methods, and in these methods, you can intercept notifications as they are hit, then extract the data it contains. Details are in the comments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class NotificationDelegate : UNUserNotificationCenterDelegate { [Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")] public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler) { //when app is opened, notification hits here first when it arrives completionHandler(UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Sound); } // When notification is clicked, this method is called [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")] public override async void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler) { //Get the data passed via the notification. var data = response.Notification.Request.Content.UserInfo; // Take action based on Action ID switch (response.ActionIdentifier) { case "reply": // Do something break; default: // Take action based on identifier if (response.IsDefaultAction) { // Handle default action... } else if (response.IsDismissAction) { // Handle dismiss action } break; } completionHandler(); //base.DidReceiveNotificationResponse(center, response, completionHandler); } } |
To understand better notification actions in Xamarin iOS, you can go through this documentation.
Detect when notifications are dismissed by users
You might want to know when a user swiped your notification away, this might be useful when collecting metrics. We will cover how to do this only on android since on iOS, I’m yet to find a way to accomplish this.
Xamarin Android
When you receive your firebase notification, at the moment you create the notification to display to your users, adding a delete intent, and subscribing to this intent with a broadcast receiver will do the job. Here is how we do it:
- Create a pending intent which will call a broadcast receiver.
1 2 | var intent = new Intent(NotificationDismissedIntentFlag); PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, pendingIntentId, intent, PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent); |
- Add the pending intent to your notification as a delete intent
1 2 3 4 5 | var builder = new NotificationCompat.Builder(context, channelId) .SetContentTitle(title) .SetContentText(message) .SetContentIntent(pending) .SetDeleteIntent(notificationDismissedIntent) |
- Create a broadcast receiver that will respond once the notification is dismissed.
1 2 3 4 5 6 7 8 9 | [BroadcastReceiver(Enabled = true, Exported = false)] [IntentFilter(new[] { Constats.NotificationDismissedIntentFlag })] public class NotificationDismissedReceiver : BroadcastReceiver { public override async void OnReceive(Context context, Intent intent) { //... } } |
I Can’t Receive Firebase Notifications in My Apps
Since there are several steps to set up firebase push notifications on both iOS and Android, you might have missed some important steps. Here are a few reminders.
Xamarin Android
- Check your Google play services JSON file, and make sure it is the right one, and that it has the correct build action “GoogleServicesJson”
- Make sure you set your firebase notifications receiver in your app manifest.
- Check if you properly implemented the firebase service supposed to receive notifications.
If you are looking for a basic guide on how to set up firebase notifications on Android, here you will find it.
Xamarin iOS
- Check your entitlements plist, if you set firebase push notifications environment
1 2 3 | <key>aps-environment</key> <string>development</string> |
- Go to the apple developer portal, and verify whether you enabled push notifications for your current provisioning profile.
Here is an excellent documentation about implementing firebase push notifications in your Xamarin.iOS apps. It is really detailed, and will surely help.
Conclusion
Push notifications are absolutely usefull for mobile apps, and if implemented properly, can be an awesome tool for keeping users. I hope this guide helped. Here is a post you might be interested in, about implementing JWT social authentication with ASPnet core and Xamarin Essentials.
Refferences
https://stackoverflow.com/a/58553440/7442601
https://medium.com/sermet-blog/xamarin-android-firebase-push-notification-with-image-ea712c89a721
Hervé TEGUIA
Damien Doumer
Md Idiake
Damien Doumer