Xamarin forms: How to handle the notification click in ios

SreeeeSreeee INMember ✭✭✭✭✭

I have implemented the push notification on my xamarin forms ios project. Now I am able to receive notifications on my iPhones that are pushed from the postman. But when tapping the notification how can I read my model data from notification and load the PCL App.xaml.cs?

When clicking the notification in ios device I need to open a content page(Message listing page) on my PCL project. I have implemented this on my PCL project App.xaml.cs. For this, I need to load the App.xaml.cs from AppDelegate.cs with the model data received from the notification.

Following way I am loading the App.xaml.cs from Android MainActivity

LoadApplication(new App(notificationdata));

In ios DidReceiveRemoteNotification is invoking when receiving the notification for background and foreground modes, but not invoking when taps the notification.

My notification body: webContentList is my model data.

{
 "to" : "dmtfiSvBBM0:APA91bFnHkamMSYgxPuiSfdvKnU8hD_mOqrWijnENNgXVSkSgo1ILH3-uKVCU7Ez2PXXOhtDoobIyKBf5UshVfTmvjSqHgXMRTsqguKCSTjIfGnXrVP-_cNFq2sisshZO-BcfkwKTl-I",
 "collapse_key" : "type_a",
 "notification" : {
      "body" : "This is body",
     "title": "Tech Team",
     "priority":"high",
     "content_available":true
 },
 "data" : {
    "webContentList": [
        {
            "webContentDefinitionId": 818084,
            "pageTitle": "CCD Grade 3-4",
            "pageKwd": "CCD Grade 3-4",
            "pageDesc": "CCD Grade 3-4",
            "siteId": 45,
            "pageCreatedTime": 1555145959428,
            "pageUpdatedDate": 1555927274279,
            "modifier": {
                "userId": 12944,
                "applicationId": 32,
                "username": "robert.downey",
                "email": "[email protected]",
                "firstName": "Robert",
                "lastName": "Downey"
            },
            "creator": {
                "userId": 12944,
                "applicationId": 32,
                "username": "robert.downey",
                "email": "[email protected]",
                "firstName": "Robert",
                "lastName": "Downey"
            }
        }
        ]
 },
  "ttl": 3600
}

My AppDelegate.cs

    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IUNUserNotificationCenterDelegate, IMessagingDelegate
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            Rg.Plugins.Popup.Popup.Init();
            global::Xamarin.Forms.Forms.Init();
            ImageCircleRenderer.Init();
            LoadApplication(new App("", ""));

            #region Push Notification            
            Firebase.Core.App.Configure();
            if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
            {
                // iOS 10 or later
                var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
                UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) => {
                    Console.WriteLine(granted);
                });

                // For iOS 10 display notification (sent via APNS)
                UNUserNotificationCenter.Current.Delegate = this;

                // For iOS 10 data message (sent via FCM)
                //Messaging.SharedInstance.RemoteMessageDelegate = this;
            }
            else
            {
                // iOS 9 or before
                var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
                var settings = UIUserNotificationSettings.GetSettingsForTypes(allNotificationTypes, null);
                UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
            }

            UIApplication.SharedApplication.RegisterForRemoteNotifications();

            Messaging.SharedInstance.Delegate = this;
            Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
            #endregion

            return base.FinishedLaunching(app, options);
        }

        [Export("messaging:didReceiveRegistrationToken:")]
        public void DidReceiveRegistrationToken(Messaging messaging, string fcmToken)
        {
            LogInformation(nameof(DidReceiveRegistrationToken), $"Firebase registration token: {fcmToken}"
            MessagingCenter.Send<object, string>(this, "fcmtoken", fcmToken.ToString());
            Console.WriteLine($"fcmtoken received: {fcmToken}");
        }

        public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
        {
            HandleMessage(userInfo);
            // Print full message.
            LogInformation(nameof(DidReceiveRemoteNotification), userInfo);
            completionHandler(UIBackgroundFetchResult.NewData);
        }

        [Export("messaging:didReceiveMessage:")]
        public void DidReceiveMessage(Messaging messaging, RemoteMessage remoteMessage)
        {
            // Handle Data messages for iOS 10 and above.
            HandleMessage(remoteMessage.AppData);
            LogInformation(nameof(DidReceiveMessage), remoteMessage.AppData);
        }

        void HandleMessage(NSDictionary message)
        {
            //if (MessageReceived == null)
            //    return;

            //MessageType messageType;
            //if (message.ContainsKey(new NSString("aps")))
            //    messageType = MessageType.Notification;
            //else
            //    messageType = MessageType.Data;

            //var e = new UserInfoEventArgs(message, messageType);
            //MessageReceived(this, e);
        }

        public static void ShowMessage(string title, string message, UIViewController fromViewController, Action actionForOk = null)
        {
            var alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
            alert.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, (obj) => actionForOk?.Invoke()));
            fromViewController.PresentViewController(alert, true, null);
        }

        void LogInformation(string methodName, object information) => Console.WriteLine($"\nMethod name: {methodName}\nInformation: {information}");

        async Task RequestPushPermissionAsync()
        {
            // iOS10 and later (https://developer.xamarin.com/guides/ios/platform_features/user-notifications/enhanced-user-notifications/#Preparing_for_Notification_Delivery)
            // Register for ANY type of notification (local or remote):
            var requestResult = await UNUserNotificationCenter.Current.RequestAuthorizationAsync(
                UNAuthorizationOptions.Alert
                | UNAuthorizationOptions.Badge
                | UNAuthorizationOptions.Sound);


            // Item1 = approved boolean
            bool approved = requestResult.Item1;
            NSError error = requestResult.Item2;
            if (error == null)
            {
                // Handle approval
                if (!approved)
                {
                    Console.Write("Permission to receive notifications was not granted.");
                    return;
                }

                var currentSettings = await UNUserNotificationCenter.Current.GetNotificationSettingsAsync();
                if (currentSettings.AuthorizationStatus != UNAuthorizationStatus.Authorized)
                {
                    Console.WriteLine("Permissions were requested in the past but have been revoked (-> Settings app).");
                    return;
                }

                UIApplication.SharedApplication.RegisterForRemoteNotifications();
            }
            else
            {
                Console.Write($"Error requesting permissions: {error}.");
            }
        }
    }
}

Log data when receiving notification:

Method name: DidReceiveRemoteNotification
Information: {
    aps =     {
        alert =         {
            body = "This is body";
            title = "Tech Team";
        };
        "content-available" = 1;
    };
    "gcm.message_id" = 1562648547588794;
    "gcm.notification.priority" = high;
    "google.c.a.e" = 1;
    webContentList = "[{\"swcmMessage\":null,\"pageTitle\":\"CCD Grade 3-4\",\"modifier\":{\"lastname\":\"downey\",\"zipcode\":null,\"parentemail\":null,\"address2\":null,\"city\":null,\"address1\":null,\"phone2\":null,\"userid\":12944,\"enabled\":true,\"phone1\":null,\"firstname\":\"robert\",\"state\":null,\"usertype\":null,\"applicationid\":32,\"profileimageurl\":null,\"profileimagetype\":null,\"email\":\"[email protected]\",\"username\":\"robert.downey\"},\"deletable\":true,\"pagecontenttype\":\"tweets.topics\",\"previewuuid\":null,\"webcontentdefinitionid\":818084,\"pagecreatedtime\":1555145959428,\"usercreated\":12944,\"pagestatus\":\"on\",\"thumbnailimageurl\":null,\"videourl\":null,\"imageurl\":\"\",\"webcontentid\":65059,\"creator\":{\"lastname\":\"downey\",\"zipcode\":null,\"parentemail\":null,\"address2\":null,\"city\":null,\"address1\":null,\"phone2\":null,\"userid\":12944,\"enabled\":true,\"phone1\":null,\"firstname\":\"robert\",\"state\":null,\"usertype\":null,\"applicationid\":32,\"profileimageurl\":null,\"profileimagetype\":null,\"email\":\"robert\"},\"contentTemplateId\":null,\"appName\":\"services\",\"customHTML\":\"\",\"processedTime\":null,\"pageDesc\":\"CCD Grade 3-4\",\"editUrl\":\"\\/module\\/tweets-topics\\/edit-tweets-topics?webcontentid=818084\",\"pageKwd\":\"CCD Grade 3-4\",\"staticContent\":true,\"siteId\":45,\"pageUrl\":\"\\/tweets-topics\\/818084\\/1\\/ccd-grade-3-4\",\"linkType\":null,\"pageUpdatedDate\":1555927274279,\"swcmStatus\":null,\"userModified\":12944}]";
}

webContentList is my model data, I need that data when loading the App.xaml.cs. How can I parse that data from the DidReceiveRemoteNotification userInfo and load the App.xaml.cs?

Best Answers

  • GraverobberGraverobber ✭✭✭
    Accepted Answer

    @Sreeee
    I don't understand your problem or question...?
    You say, when the app is in Foreground and the user taps on the notification, then DidReceiveNotificationResponse is called.
    So handle the notification from there?

    From the apple docs

    response
    The user’s response to the notification. This object contains the original notification and the identifier string for the selected action. If the action allowed the user to provide a textual response, this parameter contains a UNTextInputNotificationResponse object.

    https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649501-usernotificationcenter?language=objc

    You can then get the UserInfo Dictionary that was send by calling

    response.Notification.Request.Content.UserInfo

    Hope it helps

  • SreeeeSreeee IN ✭✭✭✭✭
    edited July 12 Accepted Answer

    @Graverobber Thanks it helps. Added like below in DidReceiveNotificationResponse and tapping works in foreground mode, Thanks.

    [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
            public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action
            completionHandler)
            {
                completionHandler();
                NSDictionary userInfo = response.Notification.Request.Content.UserInfo;
                var myData = JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
                MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
            }
    
  • GraverobberGraverobber ✭✭✭
    Accepted Answer

    @Sreeee
    I'm a bit confused about what your code actually does. Perhaps you can explain it to me.
    My current understand is that this line:
    JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
    results into a List holding objects of type webContentList, no? Or is this some JsonConvert special feature that I'm not aware of and does something else?

    If I'm right then how does your webContentList object look like?
    I suppose it will have a parameter of type webContentDefinitionId.

    So what you actually want to do is just
    var id = myData[0].webContentDefinitionId;

    Be aware that a list can have more than one element so you might not always want to access index 0.

    If you're looking through the list for a specific id you can do something like this:
    var id = myData.Where(element => element.webContentDefinitionId == IdYoureInterestedIn);

  • ColeXColeX Xamurai
    edited 6:27AM Accepted Answer
    myData[0].webContentDefinitionId
    
  • GraverobberGraverobber ✭✭✭
    Accepted Answer

    Your questions confuse me more and more :D

    What is the difference between iOS and Android here? You say webContentList is a string but it is a string as well in iOS...
    Why don't you do the same as in iOS? Take the string, pass it to DeserializeObject and transform it into a list that way?

    public override void OnMessageReceived(RemoteMessage message)
    {
        base.OnMessageReceived(message);
        string webContentList = message.Data["webContentList"];
        Console.WriteLine("webContentList:>>" + webContentList);
        var myData = JsonConvert.DeserializeObject<List<webContentList>>(webContentList);
    }
    

    Doesn't this work?

Answers

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited July 9

    @ColeX and @Graverobber How I can get the model value in App.xaml.cs, I tried like below, but getting an exception. How can I assign the value in App.xaml.cs?
    When sending:

     MessagingCenter.Send<object,List<webContentList>>(this,"PassData",myData);
    

    App.xsml.cs:

    private List<webContentList> _webContentList;
     MessagingCenter.Subscribe<object, List<webContentList>>(this, "PassData", (object obj , List<webContentList> list)=>{
                       for (int i = 0; i < list.Count; i++)
                        {
                            _webContentList[0] = list[i];
                        }
            });
    

    Also when tapping the notification DidReceiveRemoteNotification function is not invoking. Also I have added ReceivedRemoteNotification method in AppDelegate.cs.(Since I am using iPhone 7 for testing the application.) But that function is also not invoking when tapping the notification.

    DidReceiveRemoteNotification is invoking only when receiving the notification.

    How I can handle the tapping when multiple notifications receiving?

  • GraverobberGraverobber Member ✭✭✭
    edited July 9

    What is the exception that you get?

    When you send your message you're using MessagingCenter.Send<object,List<webContentList>>(this,"PassData",myData); but when you Subscribe you subscribe to List<YourModel> I guess that can't work.

    Also when tapping the notification DidReceiveRemoteNotification function is not invoking. Also I have added ReceivedRemoteNotification method in AppDelegate.cs.(Since I am using iPhone 7 for testing the application.) But that function is also not invoking when tapping the notification.

    DidReceiveRemoteNotification is invoking only when receiving the notification.

    What iOS version is your iPhone 7 running on? I guess higher than 10.
    As said

    Even if your target is lower than 10, if the device is running 10+ it will invoke DidReceiveNotificationResponse. If you support lower than 10 devices, then you have to handle the Tap in BOTH methods.
    The tap callback will be called for every single notification tapped. So

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited July 9

    @ColeX DidReceiveRemoteNotification is invoking after removing the "content-available" : 1 value. But I can't subscribe my model data in App.xaml.cs. What mistake I have done on subscribe part?

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited July 9

    @ColeX and @Graverobber
    Getting the following exception when sending model data through the messaging center.

    System.Reflection.TargetInvocationException
    Exception has been thrown by the target of an invocation
    

    @Graverobber I am not using List<YourModel> in code, I am using List<webContentList> when subscribe. It happened by mistake in the comment.

  • ColeXColeX Member, Xamarin Team Xamurai

    Your mean the exception occurs at MessagingCenter.Send<object,List<YourModel>>(this,"PassData",myData); ?

    Debug on the value of myData to check if it is null.

  • SreeeeSreeee INMember ✭✭✭✭✭

    Hi @ColeX and @Graverobber
    Following is the content page loading implementation code in App.xaml.cs. Currently, the Android app is loading the page using this code. So I am trying to use the same code for ios apps. Here I need the _webContentList value for this implementation. But I can't subscribe the vale here properly from MessagingCenter

    if (_webContentList[0]?.pageContentType.ToLower() == "tweets.group")
                        {
                            Utility.showGroupTab = true;
                            isGroup = true;
                        }
                        else
                        {
                            Utility.showGroupTab = false;
                            isGroup = false;
                        }
                        Utility.isNotificationTap = true;
                        Application.Current.Properties["siteId"] = _webContentList[0]?.siteId.ToString();
                        Application.Current.Properties["applicationId"] = "0";
                        Application.Current.Properties["mainapplicationId"] = "0";                    
                            Application.Current.Properties["isdeletegroup"] = "false";
                        MainPage = new DashBoardPage(_webContentList[0], isGroup);
    
  • GraverobberGraverobber Member ✭✭✭

    Is your _webContentList initialised when you try to access its 0 index?
    Also it is better to compare strings with Equals method not ==.

    Can you debug and see at which line it is throwing the exception?

    You say this code is from your application, is it even initialised when you try to access Application.Current?
    Is Utility already initialised?

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited July 9

    @ColeX said:
    Your mean the exception occurs at MessagingCenter.Send<object,List<YourModel>>(this,"PassData",myData); ?

    Debug on the value of myData to check if it is null.

    myData is not null. The issue is on the subscribe part. I have a local variable on App.xaml.cs. private List<webContentList> _webContentList; I need to assign the messaging center value into this variable.

      private List<webContentList> _webContentList;
     MessagingCenter.Subscribe<object, List<webContentList>>(this, "PassData", (object obj , List<webContentList> 
      list)=>{
                       for (int i = 0; i < list.Count; i++)
                        {
                            //is this work?
                            _webContentList = list;
                        }
            });
    

    I am working with this approach, I will update the result here.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @ColeX & @Graverobber Currently tapping working fine when the app is in background. Following changes I have done.

    AppDelegate.cs: DidReceiveRemoteNotification

    var myData = JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
                Console.WriteLine($"myData received: {myData}");
                if (UIApplication.SharedApplication.ApplicationState.Equals(UIApplicationState.Active))
                {
                    //App is in foreground. no actions
                }
                else
                {
                    MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
                }
    

    App.xaml.cs

    MessagingCenter.Subscribe<object, List<webContentList>>(this, "messagedata", (object obj, List<webContentList> list) => {
                    if(list != null)
                    {
                        if (list[0]?.pageContentType.ToLower() == "tweets.group")
                        {
                            Utility.showGroupTab = true;
                            isGroup = true;
                        }
                        else
                        {
                            Utility.showGroupTab = false;
                            isGroup = false;
                        }
                        Utility.isNotificationTap = true;
                        Application.Current.Properties["siteId"] = list[0]?.siteId.ToString();
                        Application.Current.Properties["applicationId"] = "0";
                        Application.Current.Properties["mainapplicationId"] = "0";
                        //Application.Current.Properties["officename"] = _webContentList[0]?.pageKwd.ToString();
                        Application.Current.Properties["isdeletegroup"] = "false";
                        MainPage = new DashBoardPage(list[0], isGroup);
                    }
                });
    

    Added contentpage loading code inside of messaging center and now page is loaded and I am able to view the messages.

    But having 2 problems:

    1. When the app is not in background, the message listing page(DashBoardPage) is not showing. Showing only the homepage of the app instead of DashBoardPage. But loading messages userdialogs is showing on home screen on this scenario. Why DashBoardPage is not loading when the app is in killed state?
    2. No notification is showing when the app is in foreground state. Is there any way to show the notification if the app is in active state?

    Also I added all the codes inside of DidReceiveRemoteNotification for handling remote notification(My ios version is 12.3.1). Should I add the same codes inside of ReceivedRemoteNotification for supporting ios version less than 10?

  • GraverobberGraverobber Member ✭✭✭

    @Sreeee

    I will answer your questions backwards:
    ReceivedRemoteNotification is only for silent notifications but as you only use user visible notifications you don't need to implement ReceivedRemoteNotification. Reference https://docs.microsoft.com/en-us/dotnet/api/uikit.uiapplicationdelegate.receivedremotenotification?view=xamarin-ios-sdk-12

    That your notification is not showing when your app is in foreground is the expected behaviour. In order to handle the Notification in Foreground your AppDelegate has to implement the WillPresentNotification method. https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter?language=objc
    Note this method is again for iOS 10+
    For iOS lower than 10 you can check in your DidReceiveRemoteNotification if the application state was active when receiving the notification.
    if (application.applicationState == UIApplicationStateActive) { //Received in foreground }

    Here you have multiple options. Either you create a LocalNotificaiton and display it which will be a similar behaviour like a background notification. You can also display an alert telling the user what happened and on OK the task is executed. Or you just do what you want to do with the tapped notification without letting the user know. It is up to you.

    For your first question, I'm not sure what the reason is. It might be that when your app was in killed state and the notification tap opens up your AppDelegate, that your Apps life cycle sends the message before the Subscribe happened.

  • SreeeeSreeee INMember ✭✭✭✭✭

    For your first question, I'm not sure what the reason is. It might be that when your app was in the killed state and the notification tap opens up your AppDelegate, that your Apps life cycle sends the message before the Subscribe happened.

    @Graverobber Please see the AppDelegate.cs FinishedLaunching method. I have added the following lines for loading the app for normal cases(Normal app opening without notification tap).

    LoadApplication(new App());
    

    Is FinishedLaunching method invoke when tapping the notification in the killed state? I think when tapping the notification FinishedLaunching method is also invoking.

    My FinishedLaunching

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
            {
                Rg.Plugins.Popup.Popup.Init();
                global::Xamarin.Forms.Forms.Init();
                ImageCircleRenderer.Init();
                LoadApplication(new App("", ""));
    
                #region Push Notification            
                Firebase.Core.App.Configure();
                if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
                {
                    // iOS 10 or later
                    var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
                    UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) => {
                        Console.WriteLine(granted);
                    });
    
                    // For iOS 10 display notification (sent via APNS)
                    UNUserNotificationCenter.Current.Delegate = this;
    
                    // For iOS 10 data message (sent via FCM)
                    //Messaging.SharedInstance.RemoteMessageDelegate = this;
                }
                else
                {
                    // iOS 9 or before
                    var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
                    var settings = UIUserNotificationSettings.GetSettingsForTypes(allNotificationTypes, null);
                    UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
                }
    
                UIApplication.SharedApplication.RegisterForRemoteNotifications();
    
                Messaging.SharedInstance.Delegate = this;
                Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
                #endregion
    
                return base.FinishedLaunching(app, options);
            }
    
  • GraverobberGraverobber Member ✭✭✭

    Yes FinishedLaunching is also invoked when the app opens from a killed state while tapping on the notification.
    When this happens, the options dictionary will have a parameter called UIApplicationLaunchOptionsRemoteNotificationKey.

    You can check for this key and act accordingly:

    if(options.Contains(new NSString("UIApplicationLaunchOptionsRemoteNotificationKey")))
    {
        //app was not running and launched due to notification tap, get the notification to process it
        NSDictionary notification = options.ObjectForKey(new NSString("UIApplicationLaunchOptionsRemoteNotificationKey")) as NSDictionary;
    
    } 
    
  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber said:
    Yes FinishedLaunching is also invoked when the app opens from a killed state while tapping on the notification.
    When this happens, the options dictionary will have a parameter called UIApplicationLaunchOptionsRemoteNotificationKey.

    You can check for this key and act accordingly:

    if(options.Contains(new NSString("UIApplicationLaunchOptionsRemoteNotificationKey")))
    

    {
    //app was not running and launched due to notification tap, get the notification to process it
    NSDictionary notification = options.ObjectForKey(new NSString("UIApplicationLaunchOptionsRemoteNotificationKey")) as NSDictionary;

    }

    Tried like this inside of FinishedLaunching but getting the following error:

    NSDictionary' does not contain a definition for 'Contains' and the best extension method overload 'ParallelEnumerable.Contains(ParallelQuery, NSString)' requires a receiver of type 'ParallelQuery'

  • GraverobberGraverobber Member ✭✭✭

    Yes sorry I was writing out of my head, you can also try with

            if(options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) != null)
    
  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber said:
    Yes sorry I was writing out of my head, you can also try with

            if(options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) != null)
    

    Getting NullReferenceException when I tried like this.

    Unhandled Exception:
    System.NullReferenceException: Object reference not set to an instance of an object
      at MyProject.iOS.AppDelegate.FinishedLaunching (UIKit.UIApplication app, Foundation.NSDictionary options) [0x00013] in /Users/mycompany/Projects/my-mobile-app/sreejith.sreenivasan/my-mobile-app.git/MyProject/MyProject.iOS/AppDelegate.cs:38 
      at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
      at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.10.0.157/src/Xamarin.iOS/UIKit/UIApplication.cs:86 
      at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0000e] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.10.0.157/src/Xamarin.iOS/UIKit/UIApplication.cs:65 
      at SmartWCM.iOS.Application.Main (System.String[] args) [0x00001] in /Users/Mycompany/Projects/my-app/sreejith.sreenivasan/my-mobile-app.git/MyProject/MyProject.iOS/Main.cs:17 
    2019-07-11 08:51:07.267 SmartWCM.iOS[702:2233572] Unhandled managed exception: Object reference not set to an instance of an object (System.NullReferenceException)
      at MyProject.iOS.AppDelegate.FinishedLaunching (UIKit.UIApplication app, Foundation.NSDictionary options) [0x00013] in /Users/mycompany/Projects/my-mobile-app/sreejith.sreenivasan/my-mobile-app.git/MyProject/MyProject.iOS/AppDelegate.cs:38 
      at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
      at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.10.0.157/src/Xamarin.iOS/UIKit/UIApplication.cs:86 
      at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0000e] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.10.0.157/src/Xamarin.iOS/UIKit/UIApplication.cs:65 
      at SmartWCM.iOS.Application.Main (System.String[] args) [0x00001] in /Users/mycomapny/Projects/my-app/sreejith.sreenivasan/my-mobile-app.git/MyProject/MyProject.iOS/Main.cs:17 
    
  • GraverobberGraverobber Member ✭✭✭

    @Sreeee
    When you start your app normally then there is no options dictionary.
    So, please try
    if(options != null && options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) != null)

    But you should have been able to figure this out by yourself.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber said:
    @Sreeee
    When you start your app normally then there is no options dictionary.
    So, please try
    if(options != null && options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) != null)

    But you should have been able to figure this out by yourself.

    Sorry I don't think like that, now exception gone and app is successfully launching normally.

    But app is breaking when tap the notification in the killed state. I added the same code from the DidReceiveRemoteNotification. In this case I can't debug the code since app is lunching from killed state. So can you please go through the following code and suggest a solution.

    AppDelegate - FinishedLaunching:

    if (options != null && options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) != null)
                {
                    //app was not running and launched due to notification tap, get the notification to process it
                    NSDictionary notification = options.ObjectForKey(new NSString("UIApplicationLaunchOptionsRemoteNotificationKey")) as NSDictionary;
                    var myData = JsonConvert.DeserializeObject<List<webContentList>>(notification[new NSString("webContentList")] as NSString);
                    MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
                }
                else
                {
                    LoadApplication(new App();
                }
    
  • GraverobberGraverobber Member ✭✭✭

    I see what you mean.
    You can try to see the console logs with Xcode perhaps there is some useful information.
    Two things you can try:
    Open Xcode -> Window -> Devices and Simulators, go to the Devices Tab, select your device then Click open Console. Now start your app again and let it crash. See if there is any useful log into the Console.

    Second is open Xcode -> Window -> Devices and Simulators, go to the Devices Tab, select your device click on View device logs. It will display the crash reports of this device. Select the ones of your app and see what it says.

    You can also add some Debug.WriteLine informations into your AppDelegate and log for example the content of your notification.

    Regarding your code I can not tell what exactly is the problem. One thing I see is:

    First you check
    options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey)
    but then you take
    options.ObjectForKey(new NSString("UIApplicationLaunchOptionsRemoteNotificationKey")) as NSDictionary

    Most likely the two values are the same but it would be better to use UIApplication.LaunchOptionsRemoteNotificationKey in both cases.

    Also, why do you not load your application in the notification tap case? I think you need to load the application regardless if opened manually or by notification tap.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber

    I have used same key for both cases but no change. App breaks when tapping the notification from the killed state.

    I am using visual studio for mac for development. In that I can't see the logs after clearing the app from background.

    Also, why do you not load your application in the notification tap case? I think you need to load the application regardless if opened manually or by notification tap.

    If I clear the application from the background and at that same time a notification comes. So if the user taps the notification at that time I need to load corresponding message listing page. At this scenario also I need to load the app and show the corresponding page. But now showing the homepage only. Hope you understand what I mean.

  • GraverobberGraverobber Member ✭✭✭

    Did you try to see some logs as I suggested with Xcode?

    Two things you can try:
    Open Xcode -> Window -> Devices and Simulators, go to the Devices Tab, select your device then Click open Console. Now start your app again and let it crash. See if there is any useful log into the Console.

    Second is open Xcode -> Window -> Devices and Simulators, go to the Devices Tab, select your device click on View device logs. It will display the crash reports of this device. Select the ones of your app and see what it says.

    This usually works for me regardless if Visual Studio or not. In the end it is an ipa running on your device, it will generate crash logs.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @ColeX @LandLu and @Graverobber

    I have added following codes for showing the notification on foreground mode in ios, and it is working fine. But tapping the notification when the app is in foreground is not working.

    [Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
    public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, 
    Action<UNNotificationPresentationOptions> completionHandler)
    {
        Console.WriteLine("Handling iOS 11 foreground notification");
        completionHandler(UNNotificationPresentationOptions.Sound | UNNotificationPresentationOptions.Alert);
    }
    
    [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
    public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action 
    completionHandler)
    {
        completionHandler();
    }
    

    I have added MessagingCenter code in DidReceiveRemoteNotification active state if block.

    public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
        {
            LogInformation(nameof(DidReceiveRemoteNotification), userInfo);
    
            completionHandler(UIBackgroundFetchResult.NewData);
    
            var myData = JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
            Console.WriteLine($"myData received: {myData}");
            if (UIApplication.SharedApplication.ApplicationState.Equals(UIApplicationState.Active))
            {
                //App is in foreground. Act on it.
                MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
            }
            else
            {
                MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
            }
        }
    

    But when tapping notification in foreground mode code execution not coming on this method. Code execution goes DidReceiveNotificationResponse. How I can manage the notification tap there. I need to parse the model data from notification and call MessagingCenter with that data. **
    **My Notification body:

    {
     "to" : "dmtfiSvBBM0:APA91bFnHkamMSYgxPuiSfdvKnU8hD_mOqrWijnENNgXVSkSgo1ILH3-uKVCU7Ez2PXXOhtDoobIyKBf5UshVfTmvjSqHgXMRTsqguKCSTjIfGnXrVP-_cNFq2sisshZO-BcfkwKTl-I",
     "collapse_key" : "type_a",
     "notification" : {
          "body" : "This is body",
         "title": "Tech Team",
         "priority":"high",
         "content_available":true
     },
     "data" : {
        "webContentList": [
            {
                "webContentDefinitionId": 818084,
                "pageTitle": "CCD Grade 3-4",
                "pageKwd": "CCD Grade 3-4",
                "pageDesc": "CCD Grade 3-4",
                "siteId": 45,
                "pageCreatedTime": 1555145959428,
                "pageUpdatedDate": 1555927274279,
                "modifier": {
                    "userId": 12944,
                    "applicationId": 32,
                    "username": "robert.downey",
                    "email": "[email protected]",
                    "firstName": "Robert",
                    "lastName": "Downey"
                },
                "creator": {
                    "userId": 12944,
                    "applicationId": 32,
                    "username": "robert.downey",
                    "email": "[email protected]",
                    "firstName": "Robert",
                    "lastName": "Downey"
                }
            }
            ]
     },
      "ttl": 3600
    }
    
  • GraverobberGraverobber Member ✭✭✭
    Accepted Answer

    @Sreeee
    I don't understand your problem or question...?
    You say, when the app is in Foreground and the user taps on the notification, then DidReceiveNotificationResponse is called.
    So handle the notification from there?

    From the apple docs

    response
    The user’s response to the notification. This object contains the original notification and the identifier string for the selected action. If the action allowed the user to provide a textual response, this parameter contains a UNTextInputNotificationResponse object.

    https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649501-usernotificationcenter?language=objc

    You can then get the UserInfo Dictionary that was send by calling

    response.Notification.Request.Content.UserInfo

    Hope it helps

  • SreeeeSreeee INMember ✭✭✭✭✭
    edited July 12 Accepted Answer

    @Graverobber Thanks it helps. Added like below in DidReceiveNotificationResponse and tapping works in foreground mode, Thanks.

    [Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
            public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action
            completionHandler)
            {
                completionHandler();
                NSDictionary userInfo = response.Notification.Request.Content.UserInfo;
                var myData = JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
                MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
            }
    
  • SreeeeSreeee INMember ✭✭✭✭✭

    Hi @Graverobber Currently the notification tapping is working fine in foreground and background mode. The only remaining problem is with the killed state. When the app is in the killed state only the home page is loading, not showing the message listing page. But before showing the home page in the UI, the message listing show progress(acr userdialogs) is also present in the UI. I have added below codes in FinishedLaunching method.

    if (options != null && options.ObjectForKey(UIApplication.LaunchOptionsRemoteNotificationKey) != null)
            {
                //app was not running and launched due to notification tap, get the notification to process it
                Console.WriteLine($"Inside if");
                NSDictionary notification = options.ObjectForKey(new NSString(UIApplication.LaunchOptionsRemoteNotificationKey)) as NSDictionary;
                Console.WriteLine($"notification received: {notification}");
                var myData = JsonConvert.DeserializeObject<List<webContentList>>(notification[new NSString("webContentList")] as NSString);
                Console.WriteLine($"myData received: {myData}");
                MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
            }
            else
            {
                Console.WriteLine($"Inside else");
                LoadApplication(new App("", ""));
            }
    

    Xcode -> Window -> Devices and Simulators, go to the Devices Tab, select your device click on View device logs. It will display the crash reports of this device. Select the ones of your app and see what it says.

    The app is launching in the normal case but crashing when open from the killed state notification tap. As per your suggestion, I use xcode for the device logs. I am adding the log details with this comment. Can you please go through the crash log file, I go through it, but didn't get anything useful.

  • GraverobberGraverobber Member ✭✭✭

    I'm sorry I don't have much time to read your crash report and after a quick look I neither can tell you what exactly the problem is.

    However when you say

    but before showing the home page in the UI, the message listing show progress(acr userdialogs) is also present in the UI

    that means that the App, when killed, is going into the first case and sending the messaging through messaging center?
    If yes, then it would be interesting to see the code that is executed when the message is received (the one that causes the progress dialog) perhaps something wrong is going on there.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber and @ColeX

    My Notification body:

    {
     "to" : "dmtfiSvBBM0:APA91bFnHkamMSYgxPuiSfdvKnU8hD_mOqrWijnENNgXVSkSgo1ILH3-uKVCU7Ez2PXXOhtDoobIyKBf5UshVfTmvjSqHgXMRTsqguKCSTjIfGnXrVP-_cNFq2sisshZO-BcfkwKTl-I",
     "collapse_key" : "type_a",
     "notification" : {
          "body" : "This is body",
         "title": "Tech Team",
         "priority":"high",
         "content_available":true
     },
     "data" : {
        "webContentList": [
            {
                "webContentDefinitionId": 818084,
                "pageTitle": "CCD Grade 3-4",
                "pageKwd": "CCD Grade 3-4",
                "pageDesc": "CCD Grade 3-4",
                "siteId": 45,
                "pageCreatedTime": 1555145959428,
                "pageUpdatedDate": 1555927274279,
                "modifier": {
                    "userId": 12944,
                    "applicationId": 32,
                    "username": "robert.downey",
                    "email": "[email protected]",
                    "firstName": "Robert",
                    "lastName": "Downey"
                },
                "creator": {
                    "userId": 12944,
                    "applicationId": 32,
                    "username": "robert.downey",
                    "email": "[email protected]",
                    "firstName": "Robert",
                    "lastName": "Downey"
                }
            }
            ]
     },
      "ttl": 3600
    }
    

    I am parsing the webContentList value using the below code:

    var myData = JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
    

    From myData how can I parse the value of webContentDefinitionId? I tried like below but that is not the correct way.

    var id = JsonConvert.DeserializeObject<string<webContentDefinitionId>> (myData[new NSString("webContentDefinitionId")] as NSString);
    
  • GraverobberGraverobber Member ✭✭✭
    Accepted Answer

    @Sreeee
    I'm a bit confused about what your code actually does. Perhaps you can explain it to me.
    My current understand is that this line:
    JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
    results into a List holding objects of type webContentList, no? Or is this some JsonConvert special feature that I'm not aware of and does something else?

    If I'm right then how does your webContentList object look like?
    I suppose it will have a parameter of type webContentDefinitionId.

    So what you actually want to do is just
    var id = myData[0].webContentDefinitionId;

    Be aware that a list can have more than one element so you might not always want to access index 0.

    If you're looking through the list for a specific id you can do something like this:
    var id = myData.Where(element => element.webContentDefinitionId == IdYoureInterestedIn);

  • ColeXColeX Member, Xamarin Team Xamurai
    edited 6:27AM Accepted Answer
    myData[0].webContentDefinitionId
    
  • SreeeeSreeee INMember ✭✭✭✭✭
    edited 7:11AM

    @Graverobber & @ColeX

    I need the webcontentid value from notification for auto-refreshing the groups. Now I pass the id through messagingcenter and do auto-refreshing. You are right the myData here is a list of type webContentList and I am a bit confused about that. Thanks :)

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber and @ColeX

    The same way I need to parse the webcontentid value in android. I can get the webContentList value from RemoteMessage like below, but how I can parse the webcontentid from that? Here webContentList is a string.

        public override void OnMessageReceived(RemoteMessage message)
        {
            base.OnMessageReceived(message);
            string webContentList = message.Data["webContentList"];
            Console.WriteLine("webContentList:>>" + webContentList);
        }
    
  • GraverobberGraverobber Member ✭✭✭
    Accepted Answer

    Your questions confuse me more and more :D

    What is the difference between iOS and Android here? You say webContentList is a string but it is a string as well in iOS...
    Why don't you do the same as in iOS? Take the string, pass it to DeserializeObject and transform it into a list that way?

    public override void OnMessageReceived(RemoteMessage message)
    {
        base.OnMessageReceived(message);
        string webContentList = message.Data["webContentList"];
        Console.WriteLine("webContentList:>>" + webContentList);
        var myData = JsonConvert.DeserializeObject<List<webContentList>>(webContentList);
    }
    

    Doesn't this work?

  • SreeeeSreeee INMember ✭✭✭✭✭

    @Graverobber I don't know it is possible to pass the string to DeserializeObject and transform it into a list. :#

    It worked, thanks :D

Sign In or Register to comment.