After upgrading my app from Xamarin.Forms 4.1.0.709244 to 4.2.0.709249 (or any package version newer), code that was working previously without fail now no longer works and crashes, but only under one specific circumstance.
My app receives push notifications via the OneSignal package, and the expected behavior is:
- A notification arrives and is opened
- The notification opened handler saves the payload to
App
class parameters and sets Application.Current.MainPage = new MainPage();
- Logic inside the
MainPage
initializer parses the payload to determine what page the user needs to be navigated to, and creates a Page object for that new page.
Detail
page is set to the new page
Under 4.1, this worked as expected. Clicking on the notification, the user would be taken to the corresponding page in the app under all scenarios. After upgrading to 4.2+, this scenario fails if the app is not currently running, but works as expected if the app is running and either in foreground or in background.
When it fails, instead of the app loading and then the user being taken to the correct page, the app loads but then just sits at the default main page (as if Detail hadn't been set using the info from the notification payload). As well, at this point if you tap on the hamburger icon the navigation menu displays briefly and the app immediately crashes with a null reference exception seen below:
Xamarin Exception Stack:
System.NullReferenceException: Object reference not set to an instance of an object
at Xamarin.Forms.Platform.Android.AppCompat.Platform.Xamarin.Forms.Platform.Android.IPlatformLayout.OnLayout (System.Boolean changed, System.Int32 l, System.Int32 t, System.Int32 r, System.Int32 b) [0x0002b] in <596751900f1f46919eb25349c2e7053a>:0
at Xamarin.Forms.Platform.Android.PlatformRenderer.OnLayout (System.Boolean changed, System.Int32 l, System.Int32 t, System.Int32 r, System.Int32 b) [0x00025] in <596751900f1f46919eb25349c2e7053a>:0
at Android.Views.ViewGroup.n_OnLayout_ZIIII (System.IntPtr jnienv, System.IntPtr native__this, System.Boolean changed, System.Int32 l, System.Int32 t, System.Int32 r, System.Int32 b) [0x00009] in <21b22bf2aca24508938d2117f4c11761>:0
at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.28(intptr,intptr,bool,int,int,int,int)
As this scenario only occurs when the app is closed and started via the notification action, I cannot use the Visual Studio debugger to identify exactly where the exception is occurring. I used Microsoft AppCenter to add a number of events that would track progress through the various methods and then report where in the logic flow the crash occurred, but all expected steps are progressing to completion. The step to set the Detail page to be the new page is being called, and the isPresentedChanged handler that is called when the hamburger icon is tapped is also running to completion.
I have reviewed the release notes for 4.2 here and while there are a few fixes related to layout of page that might be related to the crash, I get the feeling the crash is more a symptom of the change to the Detail page not happening correctly than a problem I need to solve outright. I have found a few similar-sounding errors reported to the Xamarin team, but they are all closed out as resolved and are not exactly the same problem. See here, here, here, here, and here for those.
NotificationServices.cs (Service extension for OneSignal; Code now fails regardless of which userNotificationType
is passed):
public class NotificationServices
{
public static void HandleNotificationOpened(OSNotificationOpenedResult result)
{
string notificationActionId = "HandleMessage";
OSNotificationPayload payload = result.notification.payload;
string message = payload.body;
App.NotificationActionId = notificationActionId;
App.NotificationData = payload.additionalData;
Application.Current.MainPage = new MainPage();
}
public static Page GetPageFromNotificationData()
{
Page ReturnPage;
ApiServices _apiServices = new ApiServices(); //service to make REST calls to backend system
user_notification_type userNotificationType = user_notification_type.None;
int fromUserId = 0;
int assocId = 0;
int msgId = 0;
if (App.NotificationData != null)
{
if (App.NotificationData.ContainsKey("assocId"))
{
Int32.TryParse(Convert.ToString(App.NotificationData["assocId"]), out assocId);
}
if (App.NotificationData.ContainsKey("fromUserId"))
{
Int32.TryParse(Convert.ToString(App.NotificationData["fromUserId"]), out fromUserId);
}
if (App.NotificationData.ContainsKey("msgId"))
{
Int32.TryParse(Convert.ToString(App.NotificationData["msgId"]), out msgId);
}
if (App.NotificationData.ContainsKey("userNotificationType"))
{
int unInt = 0;
Int32.TryParse(Convert.ToString(App.NotificationData["userNotificationType"]), out unInt);
userNotificationType = (user_notification_type)unInt;
}
}
switch (userNotificationType)
{
case user_notification_type.ChatMessage:
TeamBasic tm = new TeamBasic();
tm.OwnerID = fromUserId;
tm.OwnerName = _apiServices.GetUserName(fromUserId).Result;
ReturnPage = new ChatPage(tm);
break;
case user_notification_type.None:
ReturnPage = default(Page);
break;
default:
UserNotification unItem = new UserNotification();
var unList = _apiServices.GetUserNotifications(assocId, msgId+1, 1).Result;
unItem = unList[0];
UserNotificationDetailViewModel undVm = new UserNotificationDetailViewModel(unItem);
ReturnPage = new UserNotificationDetailPage(undVm);
break;
}
return ReturnPage;
}
}
Mainpage.xaml.cs:
public partial class MainPage : MasterDetailPage
{
private IHubServices _hubServices;
Dictionary<int, NavigationPage> MenuPages = new Dictionary<int, NavigationPage>();
public MainPage()
{
InitializeComponent();
_hubServices = DependencyService.Get<IHubServices>(); //signalR
_hubServices.Connect();
_hubServices.ClearPageCache += ClearPageCache;
MasterBehavior = MasterBehavior.Popover;
NavigationPage navPage = (NavigationPage)Detail;
ConnectionState cs = _hubServices.GetConnectionState().Result;
if (cs == ConnectionState.Connected)
{
var pg = NotificationServices.GetPageFromNotificationData(); //call to get page as specified per notification
Detail = new NavigationPage(pg); //this is where the Detail page should be getting updated but is acting like it isn't when the app is started by the act of opening the notification
MenuPages.Add((int)MenuItemType.LogOut, (NavigationPage)Detail);
}
else
{
App.CheckForNotificationRedirect = true;
MenuPages.Add((int)MenuItemType.About, (NavigationPage)Detail);
}
IsPresentedChanged += (sender, args) =>
{
try {
//anything
}
catch (Exception ex)
{
//anything
}
//it is after this has completed executing that the app is crashing. No exception ever occurs in the try/catch
};
}
}
Full dump as provided by App Center:
Xamarin Exception Stack:
System.NullReferenceException: Object reference not set to an instance of an object
at Xamarin.Forms.Platform.Android.AppCompat.Platform.Xamarin.Forms.Platform.Android.IPlatformLayout.OnLayout (System.Boolean changed, System.Int32 l, System.Int32 t, System.Int32 r, System.Int32 b) [0x0002b] in <596751900f1f46919eb25349c2e7053a>:0
at Xamarin.Forms.Platform.Android.PlatformRenderer.OnLayout (System.Boolean changed, System.Int32 l, System.Int32 t, System.Int32 r, System.Int32 b) [0x00025] in <596751900f1f46919eb25349c2e7053a>:0
at Android.Views.ViewGroup.n_OnLayout_ZIIII (System.IntPtr jnienv, System.IntPtr native__this, System.Boolean changed, System.Int32 l, System.Int32 t, System.Int32 r, System.Int32 b) [0x00009] in <21b22bf2aca24508938d2117f4c11761>:0
at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.28(intptr,intptr,bool,int,int,int,int)
Thread 2:
0 dalvik.system.VMStack.getThreadStackTrace(VMStack.java:-2)
1 java.lang.Thread.getStackTrace(Thread.java:1538)
2 java.lang.Thread.getAllStackTraces(Thread.java:1588)
3 com.microsoft.appcenter.crashes.Crashes.saveUncaughtException(Crashes.java:1093)
4 com.microsoft.appcenter.crashes.WrapperSdkExceptionManager.saveWrapperException(WrapperSdkExceptionManager.java:58)
5 crc643f46942d9dd1fff9.PlatformRenderer.n_onLayout(PlatformRenderer.java:-2)
6 crc643f46942d9dd1fff9.PlatformRenderer.onLayout(PlatformRenderer.java:55)
7 android.view.View.layout(View.java:20740)
8 android.view.ViewGroup.layout(ViewGroup.java:6268)
9 android.widget.RelativeLayout.onLayout(RelativeLayout.java:1084)
10 android.view.View.layout(View.java:20740)
11 android.view.ViewGroup.layout(ViewGroup.java:6268)
12 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
13 android.widget.FrameLayout.onLayout(FrameLayout.java:261)
14 android.view.View.layout(View.java:20740)
15 android.view.ViewGroup.layout(ViewGroup.java:6268)
16 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
17 android.widget.FrameLayout.onLayout(FrameLayout.java:261)
18 android.view.View.layout(View.java:20740)
19 android.view.ViewGroup.layout(ViewGroup.java:6268)
20 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
21 android.widget.FrameLayout.onLayout(FrameLayout.java:261)
22 android.view.View.layout(View.java:20740)
23 android.view.ViewGroup.layout(ViewGroup.java:6268)
24 android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
25 android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
26 android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
27 android.view.View.layout(View.java:20740)
28 android.view.ViewGroup.layout(ViewGroup.java:6268)
29 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
30 android.widget.FrameLayout.onLayout(FrameLayout.java:261)
31 com.android.internal.policy.DecorView.onLayout(DecorView.java:794)
32 android.view.View.layout(View.java:20740)
33 android.view.ViewGroup.layout(ViewGroup.java:6268)
34 android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2970)