Xamarin Forms: Issue with reading notification data in foreground mode

96 Views Asked by At

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 == "Superman")
                    {
                        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

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

Utils.cs

public class Utils
{
    public static readonly string CHANNEL_ID = "CB_FCM_CHANNEL";
    public static readonly int NOTIFICATION_ID = 100;
}

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 it will show on the UI. Again if I receive a new notification, Notification2, if I tap on that, the notification 1 data is again showing on the UI. Always reading the very first notification data in foreground mode. No such issue in background mode.

Here is my demo and postman collection to recreate this issue.

How to recreate this issue:

  • Run the demo project into an android device and fetch your device FCM token. Add it on the postman collection APIs, on your device token part.
  • Then send API named Notification 1 in foreground mode. When tap on it then notification1 text will show on the UI.
  • Then if you send Notification 2 in foreground mode and tap on it again notification1 text will show on the UI instead of notification2.
  • The initial notification data is showing for all the notifications received in foreground mode. But no such issue in background mode and everything is working fine on background mode.

Referred blogs:

  1. https://learn.microsoft.com/en-us/xamarin/android/data-cloud/google-messaging/remote-notifications-with-fcm?tabs=windows
  2. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/local-notifications#handle-incoming-notifications-on-android

Update

I have added the MessagingCenter.Send inside of CreateNotificationFromIntent like below:

void CreateNotificationFromIntent(Intent intent)
{
    if (intent.Extras != null)
    {
        foreach (var key in intent.Extras.KeySet())
        {
            var notificationName = intent.Extras.GetString("NotificationName");
            var NotificationKey = intent.Extras.GetString("NotificationKey");

            Console.WriteLine("NotificationName:>>" + notificationName);
            Console.WriteLine("NotificationKey:>>" + NotificationKey);

            if (NotificationKey == "test")
            {
                if (notificationName?.Length > 0)
                {
                    isNotification = true;
                    MessagingCenter.Send<Xamarin.Forms.Application, string>(Xamarin.Forms.Application.Current, "NotificationName", notificationName);
                    LoadApplication(new App(notificationName));
                }
            }
        }
    }
}

Updated MainPage contents with MessagingCenter.Subscribe part.

public partial class MainPage : ContentPage
{
    public MainPage(string notificationName)
    {
        InitializeComponent();
        //notificationname_label.Text = notificationName;
        MessagingCenter.Subscribe<Xamarin.Forms.Application, string>(this, "NotificationName", async (sender, arg) =>
        {
            notificationname_label.Text = arg;
        });
    }
}

But when tap on notification the notification name is not showing on the UI. When I debug it by adding breakpoints, the MessagingCenter.Send and MessagingCenter.Subscribe are invoking infinte times, but the notification name is not visible on mainpage UI.

1

There are 1 best solutions below

27
Liyun Zhang - MSFT On

My issue is the first notification in foreground mode is always showing for all the notifications receiving in foreground mode.

This is because you set the UI label in the MainPage's constructor by notificationname_label.Text = notificationName;. When the app run in foreground, the App.cs and MainPage's constructor will only be called when the MainPage initializes first time.

So you can refer to official document about using INotificationManager helper class to receive the notification.

In addition, you can also using the MessagingCenter to do that. Here is a case about using MessagingCenter to notify UI that the notifiction is updated.

Update:

If you use the MessagingCenter, you can send the new notification name when you receive it. And subscribe it in the MainPage's constructor:

Send the new notification name:

MessagingCenter.Send<Xamarin.Forms.Application, string>(Xamarin.Forms.Application.Current, "NotificationName","the new notification name you want to send")

Receive the new notification name:

        public MainPage()
        {
            InitializeComponent();

            // Subscribe to a message (which the ViewModel has also subscribed to) to display an alert
            MessagingCenter.Subscribe<Xamarin.Forms.Application, string>(Xamarin.Forms.Application.Current, "NotificationName", async (sender, arg) =>
            {
                notificationname_label.Text = arg; 
            });
        }

For more information, you can refer to using MessagingCenter to notify UI that the notification is updated and the official sample about the MessagingCenter.