Xamarin Forms: Always reading the initial notification data in foreground mode, do I need to add any unsubscribe for intent after read it once?

Sreejith Sreenivasan 941 Reputation points
2023-07-12T15:46:54.88+00:00

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("&#39;", "'");
				string header = System.Net.WebUtility.UrlDecode(message.GetNotification().Title.ToString()).Replace("&#39;", "'");
				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.

User's image

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,378 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Yonglun Liu (Shanghai Wicresoft Co,.Ltd.) 48,421 Reputation points Microsoft Vendor
    2023-07-13T06:35:09.0066667+00:00

    Hello,

    but I have a minor issue in foreground mode.

    Upon investigation, this is because the wake-up method used by the application from foreground mode and at run time is different.

    By referring to Handle incoming notifications on Android, you could see that the OnNewIntent method is called when the application is in foreground mode.

    When the application is started by notification data, the Intent data will be passed to the OnCreate method. If the application is already in the foreground, the Intent data will be passed to the OnNewIntent method.

    Therefore, you should override the OnNewIntent method as in the official example.

    protected override void OnCreate(Bundle savedInstanceState)
    {
        // ...
    
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        LoadApplication(new App());
        CreateNotificationFromIntent(Intent);
    }
    
    protected override void OnNewIntent(Intent intent)
    {
        CreateNotificationFromIntent(intent);
    }
    
    void CreateNotificationFromIntent(Intent intent)
    {
        if (intent?.Extras != null)
        {
            string title = intent.GetStringExtra(AndroidNotificationManager.TitleKey);
            string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);
            DependencyService.Get<INotificationManager>().ReceiveNotification(title, message);
        }
    }
    

    Best Regards,

    Alec Liu.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.