We just implemented a new push notification feature for our Xamarin.forms app using azure notification hub as provider. On iOS the notifications work perfectly fine when app is fresh installed on iOS device, i.e. no prior version of the app is installed on device. But when I try to upgrade the existing app on iOS device by installing the newer version on top, then I don't get any notifications.
When I debugged, I found that only during app upgrade scenario, the iOS app throws a UIKit.UIKitThreadAccessException: 'UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.' Due to this exception the device doesn't get registered with Apple Push Notification Services and thus doesn't get any push notifications.
Below is the full exception with stacktrace:
UIKit.UIKitThreadAccessException: 'UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.'
UIKit.UIKitThreadAccessException
Message=UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.
Source=mscorlib
StackTrace:
at UIKit.UIApplication.EnsureUIThread () [0x00020] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/UIKit/UIApplication.cs:96
at UIKit.UIGestureRecognizer..ctor (Foundation.NSObject target, System.IntPtr action) [0x00016] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/UIGestureRecognizer.g.cs:102
at UIKit.UIGestureRecognizer..ctor (System.IntPtr sel, UIKit.UIGestureRecognizer+Token token) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/UIKit/UIGestureRecognizer.cs:66
at UIKit.UITapGestureRecognizer..ctor (System.Action`1[T] action) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/UIKit/UIGestureRecognizer.cs:208
at Xamarin.Forms.Platform.iOS.EventTracker.CreateTapRecognizer (System.Int32 numTaps, System.Action`1[T] action, System.Int32 numFingers) [0x00000] in D:\a\1\s\Xamarin.Forms.Platform.iOS\EventTracker.cs:462
at Xamarin.Forms.Platform.iOS.EventTracker.GetNativeRecognizer (Xamarin.Forms.IGestureRecognizer recognizer) [0x00049] in D:\a\1\s\Xamarin.Forms.Platform.iOS\EventTracker.cs:258
at Xamarin.Forms.Platform.iOS.EventTracker.LoadRecognizers () [0x00042] in D:\a\1\s\Xamarin.Forms.Platform.iOS\EventTracker.cs:572
at Xamarin.Forms.Platform.iOS.EventTracker.ModelGestureRecognizersOnCollectionChanged (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) [0x00000] in D:\a\1\s\Xamarin.Forms.Platform.iOS\EventTracker.cs:624
at (wrapper delegate-invoke) <Module>.invoke_void_object_NotifyCollectionChangedEventArgs(object,System.Collections.Specialized.NotifyCollectionChangedEventArgs)
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in <b912bfaf235d4ed8af62226c84967349>:0
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00009] in <b912bfaf235d4ed8af62226c84967349>:0
at System.Collections.ObjectModel.ObservableCollection`1[T].InsertItem (System.Int32 index, T item) [0x0001a] in <b912bfaf235d4ed8af62226c84967349>:0
at System.Collections.ObjectModel.Collection`1[T].Add (T item) [0x00020] in <cbddc4225b2f45f09f3a1d43a1268bc0>:0
at Xamarin.Forms.View.<.ctor>g__AddItems|14_1 (Xamarin.Forms.View+<>c__DisplayClass14_0& ) [0x00032] in D:\a\1\s\Xamarin.Forms.Core\View.cs:85
at Xamarin.Forms.View.<.ctor>b__14_0 (System.Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs args) [0x0002f] in D:\a\1\s\Xamarin.Forms.Core\View.cs:101
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedEventArgs e) [0x00018] in <b912bfaf235d4ed8af62226c84967349>:0
at System.Collections.ObjectModel.ObservableCollection`1[T].OnCollectionChanged (System.Collections.Specialized.NotifyCollectionChangedAction action, System.Object item, System.Int32 index) [0x00009] in <b912bfaf235d4ed8af62226c84967349>:0
at System.Collections.ObjectModel.ObservableCollection`1[T].InsertItem (System.Int32 index, T item) [0x0001a] in <b912bfaf235d4ed8af62226c84967349>:0
at System.Collections.ObjectModel.Collection`1[T].Add (T item) [0x00020] in <cbddc4225b2f45f09f3a1d43a1268bc0>:0
at SWAPA.MemberMobileApp.UI.Views.Login.CommonSetup () [0x002a1] in C:\MemberMobileApp\SWAPA.MemberMobileApp.UI\SWAPA.MemberMobileApp.UI\SWAPA.MemberMobileApp.UI\Views\Login.xaml.cs:369
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_1 (System.Object state) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1037
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x0000d] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1370
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:968
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:910
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1341
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:899
at ObjCRuntime.Runtime.ThreadPoolDispatcher (System.Func`1[TResult] callback) [0x00006] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/ObjCRuntime/Runtime.cs:288
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00009] in /Library/Frameworks/Xamarin.iOS.framework/Versions/Current/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1258
The only implementation that I have done in AppDelegate is to register the device with Apple Push Notification Services and then send that device registration id to main UI app to register the device with Azure notification hub.
Below is my code with some comments:
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IUNUserNotificationCenterDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
//----Block needed for App Center Testing-----
#if ENABLE_TEST_CLOUD
//Xamarin.Calabash.Start();
#endif
//----Block needed for App Center Testing-----
new SfCalendarRenderer();
LoadApplication(new App());
SfImageEditorRenderer.Init();
SfListViewRenderer.Init();
SfCheckBoxRenderer.Init();
SfPdfDocumentViewRenderer.Init();
SfPickerRenderer.Init();
Syncfusion.SfChart.XForms.iOS.Renderers.SfChartRenderer.Init();
new Syncfusion.SfAutoComplete.XForms.iOS.SfAutoCompleteRenderer();
UITabBar.Appearance.SelectedImageTintColor = UIColor.FromRGB(213, 84, 39);
// Color of the tabbar background:
UITabBar.Appearance.BarTintColor = UIColor.LightGray;
// Color of the selected tab text color:
UITabBarItem.Appearance.SetTitleTextAttributes(
new UITextAttributes()
{
TextColor = UIColor.FromRGB(213, 84, 39)
},
UIControlState.Selected);
// Color of the unselected tab icon & text:
UITabBarItem.Appearance.SetTitleTextAttributes(
new UITextAttributes()
{
TextColor = UIColor.FromRGB(65, 64, 66)
},
UIControlState.Normal);
base.FinishedLaunching(app, options);
/*Register device with Apple Push Notification Service*/
RegisterForRemoteNotifications();
if (options != null && options.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey))
{
NSDictionary userInfo = (NSDictionary)options[UIApplication.LaunchOptionsRemoteNotificationKey];
if (userInfo != null)
{
PushNotifications.IsNotifictaionClick_AppInActive = true;
// To remove all delivered notifications
RemoveAllDeliveredPushNotifications();
}
}
return true;
}
//Register device with Apple Push Notification Services
void RegisterForRemoteNotifications()
{
//register for remote notifications based on system version
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert |
UNAuthorizationOptions.Sound |
UNAuthorizationOptions.Sound,
(granted, error) =>
{
if (granted)
InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications); // Ask user for permission to receive notifications on device.
});
}
else if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var pushSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound,
new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(pushSettings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
else
{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}
UNUserNotificationCenter.Current.Delegate = this;
}
//Send the PNS handle to UI app for device installation with Azure Notificaiton Hub.
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
//Format the pns handle before registering with Azure notification hub.
PushNotifications.PNSHandle = deviceToken.DebugDescription.Replace("<", string.Empty)
.Replace(">", string.Empty)
.Replace(" ", string.Empty)
.ToUpper();
}
// Process notification when received.
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
ProcessNotification(userInfo);
}
void ProcessNotification(NSDictionary options)
{
// make sure we have a payload
if (options != null && options.ContainsKey(new NSString("aps")))
{
// get the APS dictionary and extract message payload. Message JSON will be converted
// into a NSDictionary so more complex payloads may require more processing
NSDictionary aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;
NSDictionary alertMessage = aps.ObjectForKey(new NSString("alert")) as NSDictionary;
//Extract notification content
NSString messageKey = new NSString("body");
NSString titleKey = new NSString("title");
string messageBody = alertMessage.ContainsKey(messageKey) ? alertMessage[titleKey].ToString() : string.Empty;
string title = alertMessage.ContainsKey(titleKey) ? alertMessage[titleKey].ToString() : string.Empty;
if (!string.IsNullOrWhiteSpace(title) || !string.IsNullOrWhiteSpace(messageBody))
{
var content = new UNMutableNotificationContent();
content.Title = title;
content.Body = messageBody;
var requestID = title;
var request = UNNotificationRequest.FromIdentifier(requestID, content, null);
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
// TODO: log error messages in error data table.
Debug.WriteLine($"Received request to process notification but something went wrong.");
}
});
}
}
else
{
// TODO: log error messages in error data table.
Debug.WriteLine($"Received request to process notification but there was no payload.");
}
}
[Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
completionHandler(UNNotificationPresentationOptions.Sound | UNNotificationPresentationOptions.Alert);
}
[Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action
completionHandler)
{
completionHandler();
/*Logic to open NotificationInboxPage onclick of notification when app was active */
if (!App.IsAppLaunching)
{
var modalStack = App.Current.MainPage.Navigation.ModalStack;
if (modalStack.Count > 0 && modalStack.Last().ToString().Contains("NotificationInbox"))
App.Current.MainPage.Navigation.PopModalAsync();
App.Current.MainPage.Navigation.PushModalAsync(new NotificationInboxPage());
}
App.IsAppLaunching = false;
//To remove all delivered notifications
RemoveAllDeliveredPushNotifications();
}
private void RemoveAllDeliveredPushNotifications()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
UNUserNotificationCenter.Current.RemoveAllDeliveredNotifications();
}
else
{
UIApplication.SharedApplication.CancelAllLocalNotifications();
}
}
}
The exception says I am calling UIKit method that should only be invoked from UI thread. The only action I am calling is InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications); which I am already invoking on main thread. So I am not sure what other function is the exception talking about. And why is it complaining only when I am upgrading the app and not when I am fresh installing the app.
I checked this post here but didn't get much help. At this point I am completely stuck due to this and really need some help on resolving the issue. Any idea what I might be missing?
Thanks in advance.