How to acknowledge a push notification received on mobile device programatically, without tap?

Hi

I have a mobile app written in Xamarin for Droid and IOS platforms. I'm receiving Push notifications on the lock screen for Droid via Firebase messaging and I'm receiving IOS push notifications from my server via Apple APNS as remote notifications successfully.

Each Push notification represents an alert from my server.
When I tap on the push notification it will launch my app and navigate to a list of alerts received from the server. This works fine.

Now the problem....

I also have a requirement, whereby the user can ignore the push notification and go straight into my app and the alerts list page, select the alert received and Mark As Read/Mark as Delete. If they do this, then the Push Notification on the lock screen for this alert needs to disappear.

Is it possible on Droid and IOS to acknowledge a push notification from code (not from a user's tap) ?

Thank you

Best Answers

  • mbullock976mbullock976 ✭✭
    Accepted Answer

    Hi sorry for late reply

    So far, I've managed to resolve the issue for IOS but not Android:

    For IOS:

    I can now remove an individual delivered push notification from the notification app center using a new service I've implemented. It checks the push notification payload where I look up a custom Id I set on server side, then I pull out and cache the request.identifier, which I later use to remove the notification via by shared code when app is running:

    public class PushNotificationService : IPushNotificationService
    {
        private readonly ConcurrentDictionary<Guid, Guid> _deliveredPushNotifications =
            new ConcurrentDictionary<Guid, Guid>();
    
        private readonly AutoResetEvent _getDeliveredPushNotificationsEventHandle = new AutoResetEvent(false);
    
        public bool Remove(Guid matchId)
        {
            UNUserNotificationCenter.Current.GetDeliveredNotifications(OnGetDeliveredNotificationsFromAppCenter);
    
            _getDeliveredPushNotificationsEventHandle.WaitOne(1000);
    
            var deliveredPushNotification = _deliveredPushNotifications.FirstOrDefault(m => m.Value == matchId);
    
            if (!_deliveredPushNotifications.TryRemove(deliveredPushNotification.Key, out var result)) return false;
    
            UNUserNotificationCenter.Current.RemoveDeliveredNotifications(new[]
                {deliveredPushNotification.Key.ToString().ToUpper()});
    
            Log(
                $"PushNotificationService: {nameof(Remove)}: {deliveredPushNotification.Key.ToString()}, Matchid: {matchId}");
    
            return true;
        }
    
        public void RemoveAll()
        {
            UNUserNotificationCenter.Current.RemoveAllDeliveredNotifications();
    
            Log($"PushNotificationService: {nameof(RemoveAll)}: Removed all delivered push notifications");
        }
    
        private void OnGetDeliveredNotificationsFromAppCenter(UNNotification[] notifications)
        {
            foreach (var item in notifications)
            {
                var userInfo = item.Request.Content.UserInfo;
                var eventArgs = (NSDictionary) userInfo["eventArgs"];
    
                if (!Guid.TryParse(eventArgs["matchId"].ToString(), out var matchId)) continue;
                if (!Guid.TryParse(item.Request.Identifier, out var requestId)) continue;
    
                _deliveredPushNotifications.TryAdd(requestId, matchId);
    
                Log($"PushNotificationService: Cached Request Identifier: {requestId}, Alert Matchid: {matchId}");
            }
    
            _getDeliveredPushNotificationsEventHandle.Set();
        }
    
        private void Log(string message)
        {
            AutofacContainer.Default.Resolve<IMobileLogger>()
                .Warning(() => message);
        }
    }
    

    I'll post my current android implementation of handling push notifications to see if you can help, thanks

  • mbullock976mbullock976 ✭✭
    Accepted Answer

    I've managed to get this working for Android now

    I updated the OnMessageReceived with the following:

    I pull out my custom server data 'matchId' from the incoming message and persist it to file storage along with the NotificationId i.e messageId

            if (matchId.HasValue)
            {
                DeliveredPushNotifications.TryAdd(messageId, matchId.Value);                
                _appFileStorage.Save("PushNotifications", 
                DeliveredPushNotifications).ConfigureAwait(false).GetAwaiter().GetResult();
            }   
    

    Just like my IOS implementation above, I call the PushNotificationService.Remove(matchId) from my app, and it calls the following:

    public bool Remove(Guid matchId)
    {
    var fileStorage = new ApplicationFileStorage();
    var result = fileStorage.Get<ConcurrentDictionary<int, Guid>>
    ("PushNotifications").ConfigureAwait(false).GetAwaiter().GetResult();

            var notification = result.FirstOrDefault(m => m.Value == matchId);
            if (!notification.Equals(default(KeyValuePair<int, Guid>)))
            {
                var notificationManager = (NotificationManager)Application.Context.GetSystemService(Context.NotificationService);
                notificationManager.Cancel(notification.Key);
            }
    
            return true;
        }
    

    I'll need to do some refactoring and tidy up but this now allows me to manage previously delivered push notifications from my app without the user tapping the notification to acknowledge it.

    Thanks @LeonLu for the android code !!

  • mbullock976mbullock976 ✭✭
    Accepted Answer

    @AdamMeaney Thanks, I've removed the caching of messageids on the client. I now use the matchId (GUID) from the server's alert payload and pass it in as nofiticationId (int) using GetHashCode and can remove doing the same way.

    Works a treat and no more caching/filestorage on the client required. cheers

Answers

  • 1xo21xo2 NZMember ✭✭

    can you access the notification id?
    on click - can the broadcast receiver could alter the pending intent?

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    If user open the application directly when received the notification. You need to mark the notification as read and make this notification disappear on the lock screen?

    If so, you can try to use following code to achieve that.

     var mNotificationManager= NotificationManagerCompat.From(this);
     mNotificationManager.Cancel(ID);
    
  • mbullock976mbullock976 Member ✭✭
    Accepted Answer

    Hi sorry for late reply

    So far, I've managed to resolve the issue for IOS but not Android:

    For IOS:

    I can now remove an individual delivered push notification from the notification app center using a new service I've implemented. It checks the push notification payload where I look up a custom Id I set on server side, then I pull out and cache the request.identifier, which I later use to remove the notification via by shared code when app is running:

    public class PushNotificationService : IPushNotificationService
    {
        private readonly ConcurrentDictionary<Guid, Guid> _deliveredPushNotifications =
            new ConcurrentDictionary<Guid, Guid>();
    
        private readonly AutoResetEvent _getDeliveredPushNotificationsEventHandle = new AutoResetEvent(false);
    
        public bool Remove(Guid matchId)
        {
            UNUserNotificationCenter.Current.GetDeliveredNotifications(OnGetDeliveredNotificationsFromAppCenter);
    
            _getDeliveredPushNotificationsEventHandle.WaitOne(1000);
    
            var deliveredPushNotification = _deliveredPushNotifications.FirstOrDefault(m => m.Value == matchId);
    
            if (!_deliveredPushNotifications.TryRemove(deliveredPushNotification.Key, out var result)) return false;
    
            UNUserNotificationCenter.Current.RemoveDeliveredNotifications(new[]
                {deliveredPushNotification.Key.ToString().ToUpper()});
    
            Log(
                $"PushNotificationService: {nameof(Remove)}: {deliveredPushNotification.Key.ToString()}, Matchid: {matchId}");
    
            return true;
        }
    
        public void RemoveAll()
        {
            UNUserNotificationCenter.Current.RemoveAllDeliveredNotifications();
    
            Log($"PushNotificationService: {nameof(RemoveAll)}: Removed all delivered push notifications");
        }
    
        private void OnGetDeliveredNotificationsFromAppCenter(UNNotification[] notifications)
        {
            foreach (var item in notifications)
            {
                var userInfo = item.Request.Content.UserInfo;
                var eventArgs = (NSDictionary) userInfo["eventArgs"];
    
                if (!Guid.TryParse(eventArgs["matchId"].ToString(), out var matchId)) continue;
                if (!Guid.TryParse(item.Request.Identifier, out var requestId)) continue;
    
                _deliveredPushNotifications.TryAdd(requestId, matchId);
    
                Log($"PushNotificationService: Cached Request Identifier: {requestId}, Alert Matchid: {matchId}");
            }
    
            _getDeliveredPushNotificationsEventHandle.Set();
        }
    
        private void Log(string message)
        {
            AutofacContainer.Default.Resolve<IMobileLogger>()
                .Warning(() => message);
        }
    }
    

    I'll post my current android implementation of handling push notifications to see if you can help, thanks

  • mbullock976mbullock976 Member ✭✭
    edited September 11

    This is my current Android implementation to handling push notifications that come in. So as each push notification comes I will see a list of them on my lock screen.

    This same list will exist in my app and I want the ability for the user when they read the notification with my app that it will remove the correct corresponding delivered notification from my lock screen:

    [Service(Name = "myandroidfirebasemessagingservice", Exported = true, Enabled = true)]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    [BroadcastReceiver]
    public class AndroidFirebaseMessagingService : FirebaseMessagingService
    {
        private static int _messageId = 1;
        private string _remoteMessageId = "not-set";
    
        public override void OnMessageReceived(RemoteMessage message)
        {
            if (IsApplicationInTheForeground()) return;
    
            //circumvent duplicate push notification received issue when app is closed on device.
            if (_remoteMessageId == message.MessageId) return;
    
            var notificationManager = (NotificationManager)Application.Context.GetSystemService(NotificationService);
            if (notificationManager == null) return;
    
            var userContext = GetUserContext();
            if (userContext == null || !userContext.IsLoggedIn || string.IsNullOrWhiteSpace(userContext.SessionId)) return;
    
            var encrypted = GetDataBoolValue(message, "encrypted");
            if (!encrypted) return;
            var title = GetDataStringValue(message, "title", userContext.SessionId);
            var body = GetDataStringValue(message, "body", userContext.SessionId);
            var eventName = GetDataStringValue(message, "eventName", userContext.SessionId);
            var eventArgs = GetDataStringValue(message, "eventArgs", userContext.SessionId, false);
    
            var builder = new Notification.Builder(this);
            builder.SetContentTitle(body);
            builder.SetContentText(title);
            builder.SetAutoCancel(true);
            builder.SetLocalOnly(true);
            builder.SetSmallIcon(Resource.Drawable.tray_icon);
            builder.SetColor(ContextCompat.GetColor(this, Resource.Color.design_default_color_primary));
            builder.SetPriority(1);
            builder.SetDefaults(NotificationDefaults.All);
    
            var intent = new Intent(this, typeof(MainActivity));
            intent.PutExtra("eventName", eventName);
            intent.PutExtra("eventArgs", eventArgs);
            intent.AddFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);
    
            var messageId = Interlocked.Increment(ref _messageId);
    
            var pendingIntent = PendingIntent.GetActivity(this, messageId, intent, PendingIntentFlags.UpdateCurrent);
            builder.SetContentIntent(pendingIntent);
    
            notificationManager.Notify(messageId, builder.Build());
    
            _remoteMessageId = message.MessageId;
    
            //Wake up device to show alert on lock screen for 3 secs
            PowerManager pm = (PowerManager) this.GetSystemService(Context.PowerService);
            bool isScreenOn = pm.IsInteractive; // check if screen is on
            if (!isScreenOn)
            {
                PowerManager.WakeLock wl =
                    pm.NewWakeLock(WakeLockFlags.ScreenDim | WakeLockFlags.AcquireCausesWakeup,
                        "myApp:notificationLock");
                wl.Acquire(3000); //set time in milliseconds
            }            
        }
    
        public override void HandleIntent(Intent intent)
        {
            try
            {
                if (intent.Extras != null)
                {
                    var builder = new RemoteMessage.Builder("AndroidFirebaseMessagingService");
    
                    foreach (string key in intent.Extras.KeySet())
                    {
                        builder.AddData(key, intent.Extras.Get(key).ToString());
                    }
    
                    this.OnMessageReceived(builder.Build());
                }
                else
                {
                    base.HandleIntent(intent);
                }
            }
            catch (Exception)
            {
                base.HandleIntent(intent);
            }
        }
    
        private string GetDataStringValue(RemoteMessage message, string key, string sessionId, bool encrypted = true)
        {
            if (!message.Data.ContainsKey(key)) return null;
    
            return encrypted ? PushDataDecryptor.Decrypt(message.Data[key], sessionId) : message.Data[key];
        }
    
        private bool GetDataBoolValue(RemoteMessage message, string key)
        {
            return message.Data.ContainsKey(key) && bool.Parse(message.Data[key]);
        }
    
        private IUserContext GetUserContext()
        {
            if (AutofacContainer.Default != null) return AutofacContainer.Default.Resolve<IUserContext>();
    
            var appFileStorage = new ApplicationFileStorage();
            return appFileStorage.Get<UserContext>(MobileConstants.Storage.CredentialFileName).Result;
        }
    
        private bool IsApplicationInTheForeground()
        {
            ActivityManager.RunningAppProcessInfo myProcess = new ActivityManager.RunningAppProcessInfo();
            ActivityManager.GetMyMemoryState(myProcess);
            var isInForeground = myProcess.Importance == Android.App.Importance.Foreground;
    
            return isInForeground;
        }
    }
    
  • mbullock976mbullock976 Member ✭✭

    @LeonLu Hi, regarding your question..Yes I want to make an individual delivered push notification disappear from my lock screen, by initiating the request from my app and not from tapping the notification on the lock screen.

    Based on you solution and my implementation above, will it be just a case for me to cache the _messageId and use it like this perhaps?

    var mNotificationManager= NotificationManagerCompat.From(this);
    mNotificationManager.Cancel(_messageId);

  • mbullock976mbullock976 Member ✭✭

    For android implementation - I'm getting closer.

    I've modified the android code above and cache messageId. Then from my app I call notificationManager.Cancel(messageId)
    This works great.

    However if I close my app I lose my cache of messageIds

    Is there a firebase API method that will allow me to get the messageids when I reopen my app e,g somehow read back in the previous delivered push notifications and so I can pull out the messageIds

    OR maybe instead of me caching the messageids in memory, I persist to file??

  • AdamMeaneyAdamMeaney USMember ✭✭✭✭✭

    You could just have an id on your alerts that you use for the message id when you send it.

    That way if they manually go to the alerts page, you can loop through the alerts in there, take their IDs, and dismiss notifications with those ids.

  • mbullock976mbullock976 Member ✭✭
    Accepted Answer

    I've managed to get this working for Android now

    I updated the OnMessageReceived with the following:

    I pull out my custom server data 'matchId' from the incoming message and persist it to file storage along with the NotificationId i.e messageId

            if (matchId.HasValue)
            {
                DeliveredPushNotifications.TryAdd(messageId, matchId.Value);                
                _appFileStorage.Save("PushNotifications", 
                DeliveredPushNotifications).ConfigureAwait(false).GetAwaiter().GetResult();
            }   
    

    Just like my IOS implementation above, I call the PushNotificationService.Remove(matchId) from my app, and it calls the following:

    public bool Remove(Guid matchId)
    {
    var fileStorage = new ApplicationFileStorage();
    var result = fileStorage.Get<ConcurrentDictionary<int, Guid>>
    ("PushNotifications").ConfigureAwait(false).GetAwaiter().GetResult();

            var notification = result.FirstOrDefault(m => m.Value == matchId);
            if (!notification.Equals(default(KeyValuePair<int, Guid>)))
            {
                var notificationManager = (NotificationManager)Application.Context.GetSystemService(Context.NotificationService);
                notificationManager.Cancel(notification.Key);
            }
    
            return true;
        }
    

    I'll need to do some refactoring and tidy up but this now allows me to manage previously delivered push notifications from my app without the user tapping the notification to acknowledge it.

    Thanks @LeonLu for the android code !!

  • mbullock976mbullock976 Member ✭✭

    @AdamMeaney Thanks, I will try this as I could avoid the whole filestorage business. It's a shame my matchId is guid as I could use that but it looks like notificationid needs to be int. So yes I can look to update my server alerts to include a unique integer int

  • mbullock976mbullock976 Member ✭✭
    Accepted Answer

    @AdamMeaney Thanks, I've removed the caching of messageids on the client. I now use the matchId (GUID) from the server's alert payload and pass it in as nofiticationId (int) using GetHashCode and can remove doing the same way.

    Works a treat and no more caching/filestorage on the client required. cheers

Sign In or Register to comment.