Below is my Notification Payload:
{
"to" : "device-fcm-token",
"notification" : {
"body" : "Hiii",
"title": "Title"
},
"data" : {
"commentMessageType": "customer",
"revealedLocationName": "Moncy Events Center",
"revealedLocationId": "77451",
"requestData":
{
"request": {
"itemId": 3226,
"itemTitle": "Need car maintanance service",
"itemDescription": "Need car maintanance service",
"itemType": "request",
"itemStatus": 1,
"itemSource": 0,
"itemPriority": 1
},
"category": "Vehicle Rental",
"companyId": 0,
"companyName": null
}
}
}
I need to read this commentMessageType, revealedLocationName, revealedLocationId and requestData
in all modes.
Below is my SendNotificatios
function:
public void SendNotificatios(string body, string Header, IDictionary<string, string> data)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
//below section added
foreach (var key in data.Keys)
{
intent.PutExtra(key, data[key]);
}
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.Mutable);
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
var notificationBuilder = new NotificationCompat.Builder(this)
.SetContentTitle(Header)
.SetContentText(body)
.SetSmallIcon(Resource.Mipmap.ic_notification)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
notificationManager.Notify(new Random().Next(), notificationBuilder.Build());
}
else
{
var notificationBuilder = new NotificationCompat.Builder(this, Utils.CHANNEL_ID)
.SetContentTitle(Header)
.SetContentText(body)
.SetSmallIcon(Resource.Mipmap.ic_notification)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
NotificationChannel channel = new NotificationChannel(Utils.CHANNEL_ID, "FCM Notifications", NotificationImportance.Default);
notificationManager.CreateNotificationChannel(channel);
notificationManager.Notify(new Random().Next(), notificationBuilder.Build());
}
}
I am reading data like below:
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
var requestData = Intent.Extras.GetString("requestData");
var commentMessageType = Intent.Extras.GetString("commentMessageType");
var revealedLocationName = Intent.Extras.GetString("revealedLocationName");
var revealedLocationId = Intent.Extras.GetString("revealedLocationId");
if (key == "requestData")
{
if (requestData?.Length > 0)
{
isNotification = true;
LoadApplication(new App(contactsService, null, requestData, commentMessageType,
revealedLocationName, revealedLocationId));
}
}
}
}
It is working fine when the app is in background or removed from background (killed mode)
but I have a minor issue in foreground mode. In foreground mode, always showing the initial notification data. When a new notification received in foreground mode, at that time also the initial notification data is reading. Is there any unsubscribe or remove data from intent after it read once? I tried updating all the Nuget packages on android platform, but no luck.
Update
I have created a demo with this issue. Below is my MainActivity
code:
using System;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
using static Firebase.Messaging.RemoteMessage;
using Android.Gms.Common;
using Android.Nfc;
using Android.Util;
using Android.Content;
using Firebase.Messaging;
using NoticationDemo.Droid;
using System.Collections.Generic;
using Xamarin.Forms;
using AndroidX.Core.App;
namespace NoticationDemo.Droid
{
[Activity(
Label = "NoticationDemo",
Icon = "@mipmap/icon",
Theme = "@style/MainTheme",
MainLauncher = true,
LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
static readonly string TAG = "MainActivity";
public bool isNotification = false;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
isNotification = false;
IsPlayServicesAvailable();
//background mode or killed mode
CreateNotificationFromIntent(Intent);
if (!isNotification)
{
LoadApplication(new App("No Notification"));
}
}
public bool IsPlayServicesAvailable()
{
int resultcode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
if (resultcode != ConnectionResult.Success)
{
if (GoogleApiAvailability.Instance.IsUserResolvableError(resultcode))
{
Console.WriteLine($"Error:{GoogleApiAvailability.Instance.GetErrorString(resultcode)}");
}
else
{
Console.WriteLine("Error: play services not supported!");
}
return false;
}
else
{
Console.WriteLine("Play services available");
return true;
}
}
protected override void OnNewIntent(Intent intent)
{
//foreground mode
CreateNotificationFromIntent(intent);
}
void CreateNotificationFromIntent(Intent intent)
{
if (intent.Extras != null)
{
foreach (var key in intent.Extras.KeySet())
{
var notificationName = intent.Extras.GetString("NotificationName");
var NotificatioKey = intent.Extras.GetString("NotificationKey");
Console.WriteLine("NotificationName:>>" + notificationName);
Console.WriteLine("NotificatioKey:>>" + NotificatioKey);
if (NotificatioKey == "test")
{
if (notificationName?.Length > 0)
{
isNotification = true;
LoadApplication(new App(notificationName));
}
}
}
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
[Service(Enabled = true, Exported = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseNotificationService : FirebaseMessagingService
{
public override void OnNewToken(string token)
{
base.OnNewToken(token);
Console.WriteLine($"Token received:>> {token}");
SendRegistrationTokenToMainPRoject(token);
}
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
try
{
string body = System.Net.WebUtility.UrlDecode(message.GetNotification().Body.ToString()).Replace("'", "'");
string header = System.Net.WebUtility.UrlDecode(message.GetNotification().Title.ToString()).Replace("'", "'");
SendNotificatios(body, header, message.Data);
}
catch (Exception ex)
{
Console.WriteLine("Error:>>" + ex);
}
}
public void SendNotificatios(string body, string Header, IDictionary<string, string> data)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
foreach (var key in data.Keys)
{
intent.PutExtra(key, data[key]);
}
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.Mutable);
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
var notificationBuilder = new NotificationCompat.Builder(this)
.SetContentTitle(Header)
.SetContentText(body)
.SetSmallIcon(Resource.Mipmap.icon)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
notificationManager.Notify(new Random().Next(), notificationBuilder.Build());
}
else
{
var notificationBuilder = new NotificationCompat.Builder(this, Utils.CHANNEL_ID)
.SetContentTitle(Header)
.SetContentText(body)
.SetSmallIcon(Resource.Mipmap.icon)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
NotificationChannel channel = new NotificationChannel(Utils.CHANNEL_ID, "FCM Notifications", NotificationImportance.Default);
notificationManager.CreateNotificationChannel(channel);
notificationManager.Notify(new Random().Next(), notificationBuilder.Build());
}
}
void SendRegistrationTokenToMainPRoject(string token)
{
try
{
//Send Refresh to you FCM Server Here
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
AndroidManifest
Utils.cs
public class Utils
{
public static readonly string CHANNEL_ID = "CB_FCM_CHANNEL";
public static readonly int NOTIFICATION_ID = 100;
}
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.noticationdemo">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<application android:label="NoticationDemo.Android" android:theme="@style/MainTheme">
<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.ACCESS_NETWORK_STATE" />
</manifest>
Notification Payload:
{
"to" : "device token",
"notification" : {
"body" : "Hi1",
"title": "Notification1"
},
"data" : {
"NotificationName": "notification1",
"NotificationKey" : "test"
}
}
I am reading the NotificationName
from the notification and passing it as an argument to App.xaml.cs
and showing it on the UI of Mainpage
.
public App(string notificationName)
{
InitializeComponent();
MainPage = new MainPage(notificationName);
}
public partial class MainPage : ContentPage
{
public MainPage(string notificationName)
{
InitializeComponent();
notificationname_label.Text = notificationName;
}
}
My issue is the first notification in foreground mode is always showing for all the notifications receiving in foreground mode.
For eg: I received Notification1 in foreground mode. If I tap on it the notification 1 data will read and that corresponding page will open in my app. Again if I receive a new notification, Notification2, if I tap on that, the notification 1 data is again read and notification 1 page will open in my app again. Always reading the very first notification data in foreground mode. No such issue in background mode.
I can share this demo project and sample postman collection for the easy recreation of this issue.
Update 25/07/2023
Created INotificationManager
and NotificationEventArgs
:
namespace NoticationDemo
{
public interface INotificationManager
{
event EventHandler NotificationReceived;
void ReceiveNotification(string notificationName);
}
}
public class NotificationEventArgs : EventArgs
{
public string notificationName { get; set; }
}
Created AndroidNotificationManager
and implemented the interface:
public class AndroidNotificationManager : INotificationManager
{
public event EventHandler NotificationReceived;
public void ReceiveNotification(string notiName)
{
var args = new NotificationEventArgs()
{
notificationName = notiName,
};
NotificationReceived?.Invoke(null, args);
}
}
Added DependencyService
on CreateNotificationFromIntent
:
void CreateNotificationFromIntent(Intent intent)
{
if (intent.Extras != null)
{
foreach (var key in intent.Extras.KeySet())
{
var notificationName = intent.Extras.GetString("NotificationName");
var NotificatioKey = intent.Extras.GetString("NotificationKey");
Console.WriteLine("NotificationName:>>" + notificationName);
Console.WriteLine("NotificationKey:>>" + NotificatioKey);
if (NotificatioKey == "test")
{
if (notificationName?.Length > 0)
{
isNotification = true;
DependencyService.Get<INotificationManager>().ReceiveNotification(notificationName);
LoadApplication(new App(""));
}
}
}
}
On App.xaml.cs
added the below codes:
public partial class App : Application
{
INotificationManager notificationManager;
public App(string notificationName)
{
InitializeComponent();
//MainPage = new MainPage(notificationName);
try
{
notificationManager = DependencyService.Get<INotificationManager>();
notificationManager.NotificationReceived += (sender, eventArgs) =>
{
var evtData = (NotificationEventArgs)eventArgs;
ShowNotification(evtData.notificationName);
};
void ShowNotification(string NotiName)
{
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("NotiName:>>" + NotiName);
MainPage = new MainPage(NotiName);
});
}
}
catch(Exception e)
{
MainPage = new MainPage(notificationName);
Debug.WriteLine("Exception:>>:"+e);
}
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
No change in MainPage
public partial class MainPage : ContentPage
{
public MainPage(string notificationName)
{
InitializeComponent();
notificationname_label.Text = notificationName;
}
}
But when I tap the notification getting **System.NullReferenceException:** 'Object reference not set to an instance of an object.
