Xamarin.Forms icon indicating copy to clipboard operation
Xamarin.Forms copied to clipboard

[Bug] Xamarin android - push notification Intent extras lost

Open MattePozzy opened this issue 3 years ago • 6 comments

Description

I have follow this guide to setup push notification in my Xamarin Forms app.

I have an app with two activity, one splash activity

Activity(Theme = "@style/SplashTheme",
        MainLauncher = true, ScreenOrientation = ScreenOrientation.Portrait, NoHistory = true)]
    public class SplashAct: Activity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            StartActivity(typeof(MainActivity));
            Finish();
        }
    }

and the MainActivity

 [Activity(
        ClearTaskOnLaunch = false,
        FinishOnTaskLaunch = false,
        Theme = "@style/MainTheme",
        LaunchMode = LaunchMode.SingleTask,
        ScreenOrientation = ScreenOrientation.Portrait,
        ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public partial class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, Android.Gms.Tasks.IOnSuccessListener

in the main activity I have override the OnNewIntent method

     protected override void OnNewIntent(Intent intent)
        {
            base.OnNewIntent(intent);
            ProcessNotificationActions(intent);
        }

and I have tried to send a notification using a web api published on Azure with this body:

{
    "text": "Test msg",
    "action": "action_a"
}

on device the text is correctly shown, but when the code reach the OnNewIntent method the intent has not extras so I lost the "action" value.

NB. I can't use LaunchMode = LaunchMode.SingleTop otherwise the OnNewIntent is not called, the OnCreate is called and the app shows a blank screen. This happens only if the application is in background or closed. If it is in foreground all works fine.

Expected Behavior

I can get the extra intent.

Actual Behavior

the extra intent is lost.

Reproduction Link

Attached a sample, a video and info to make the POSTMAN\RESTER request.

Archive.zip

MattePozzy avatar Feb 07 '22 16:02 MattePozzy

Having the same issue. Have you found any work arounds?

Tronald avatar Apr 06 '22 22:04 Tronald

Having the same issue. Have you found any work arounds?

Hi @Tronald, I have solve by using this code as OnCreate of the SplashActivity

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        var intent = new Intent(Application.Context, typeof(MainActivity));
        intent.PutExtras(Intent);
        StartActivity(intent);
        Finish();
    }

MattePozzy avatar Apr 07 '22 06:04 MattePozzy

@Raystorm7 thanks! Where are you pulling Intent from in your intent.PutExtras() call?

As the below code is in the MainActivity, I am unsure as to how to gather push notification intent from SplahActivity.

 protected override void OnNewIntent(Intent intent)
{
            base.OnNewIntent(intent);
            ProcessNotificationActions(intent);
}

To make things more fun, Android 28 changes splash screen behavior so we will have to figure this all out again :(

I appreciate the response!

Tronald avatar Apr 07 '22 14:04 Tronald

@Tronald in MainActivity I've done this:

    protected override void OnNewIntent(Intent intent)
    {
        base.OnNewIntent(intent);
        ProcessNotificationActions(intent);
    }

    void ProcessNotificationActions(Intent intent)
    {
        try
        {
            if (intent.Extras != null)
            {
                Bundle bundle = intent.Extras;
                Dictionary<string, object> dict = bundle.KeySet()
                    .ToDictionary<string, string, object>(key => key, key => bundle.Get(key));
            }

            if (intent?.HasExtra("action") == true)
            {
                var action = intent.GetStringExtra("action");

                if (!string.IsNullOrEmpty(action))
                {
                    OnNewIntentNotifica = true;
                    NotificationActionService.TriggerAction(action);
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }
    }

the "action" is a part of my push arguments that is like this:

{
    "text": "Test message",
    "action": "@@goToChat@@1b872cde-8a84-46d0-b3a3-153281fe818e@@",
    "tags": ["username"]
}

the NotificationActionService.TriggerAction(action) method contains the login to manage the notification, in my case I open a View with a specific chat :)

MattePozzy avatar Apr 08 '22 06:04 MattePozzy

@Raystorm7 thank you so much, you are a wizard!

I finally got it working after some trial and error. I had realized that the Microsoft provided guide has the SplashActivity inherit from AppCompatActivity instead of Activity like in your example. The guide implements code almost identical to yours so I missed it, but once I made the swap and moved my logic from OnResume to OnCreate it began to work flawlessy.

Microsoft's documentation for Xamarin Forms seems full of incorrect code I have been noticing. It has made this journey a bit tough at times lol.

Thanks for your help!

Tronald avatar Apr 10 '22 16:04 Tronald

Everything works as a charm, as long as the Notification field is removed. This is not a Xamarin bug, but an Android framework change.

Well, maybe it's late for many, but this is my Firebase Backend Code (using official Firebase.Admin.Sdk):

 public void Send(BaseNotification baseNotification)
 {
     var data = baseNotification.ToDictionary();
     ValidatePayload(data);
     var msg = new Message
     {
         Token = baseNotification.To,
         Android = new AndroidConfig()
         {
             Priority = Priority.High
         },
         Data = data
     };
     string result = FirebaseMessaging.GetMessaging(app).SendAsync(msg).Result;
     if (result == null || result.Length == 0)
     {
         throw new InvalidOperationException("Failed to deliver message.");
     }
 }

Everything works as a charm, as long as the Notification field is removed. This way, your background execution handler is invoked. Additionally, this is my relevant Xamarin code for Activity:

 [Activity(
     Label = "XyZ_Not_That_Relevant",
     Icon = "@mipmap/ic_launcher",
     Theme = "@style/MainTheme",
     MainLauncher = true,
     Exported = true,
     ResizeableActivity = false,
     LaunchMode = LaunchMode.SingleTop,
     ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]


 [IntentFilter(
     actions: new string[] { Intent.ActionView },
     Categories = new string[] { Intent.CategoryDefault },
     DataScheme = NotificationConstants.FieldScheme,
     DataHost = NotificationConstants.FieldHost
)]

From the android firebase notification service, I have the following snippet:

void SendNotification(RemoteMessage message)
{
    var payload = NotificationMarshaller.BuildNotificationFromKeyValuePair(message.Data);
    bool isAppInForeground = CheckAppIsInForeground();
    if (isAppInForeground)
    {
        //Omitted for brevity, app is in foreground :) 
        return;
    }
    var dotNetUri = NotificationMarshaller.ConvertToUri(payload);
    Android.Net.Uri javaAndroidUri = Android.Net.Uri.Parse(dotNetUri.AbsoluteUri);

    Intent intent = new Intent(BaseContext,typeof(MainActivity));
    intent.SetAction(Intent.ActionView);
    intent.SetData(javaAndroidUri);
    intent.SetFlags(ActivityFlags.FromBackground);

    PendingIntentFlags flags = PendingIntentFlags.UpdateCurrent;
    if (Build.VERSION.SdkInt >= BuildVersionCodes.S)
    {
        flags |= PendingIntentFlags.Immutable;
    }
    var rand = new Random().Next(2000);
    var pendingIntent = PendingIntent.GetActivity(this, rand, intent, flags);

    var channel = new NotificationChannelCompat.Builder(ChannelId, NotificationManagerCompat.ImportanceHigh)
       .SetDescription(ChannelDescription)
       .SetName(ChannelName)
       .Build();

    var notification = new NotificationCompat.Builder(this, channel.Id)
       .SetSmallIcon(Resource.Drawable.ic_notification_private_collaboration)
       .SetColor(Android.Graphics.Color.White.ToArgb())
       .SetContentTitle(payload.DisplayTitle)
       .SetContentText(payload.DisplayBody)
       .SetContentIntent(pendingIntent)
       
       .SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))
       .SetChannelId(channel.Id)
       .SetAutoCancel(true)
       .Build();

    var manager = NotificationManagerCompat.From(this);
    manager.CreateNotificationChannel(channel);
    manager.Notify(rand, notification);
    
}

CtrlAltDeleteMeNot avatar Nov 07 '23 10:11 CtrlAltDeleteMeNot