I'm trying to write an async method which should be called from a background thread. The method should open a modal page, read some input fields, close the modal page and return the read values.
This is my method:
public static async Task<PasswordDialogValues> OpenPasswordDialog() { var vm = new PasswordFormViewModel(); Device.BeginInvokeOnMainThread(async () => { var pwPage = new ModalContentPage() { WidthRequest = 380, HeightRequest = 310, BindingContext = vm, Content = new PasswordFormView(), }; await MainPage.Navigation.PushModalAsync(pwPage); }); var values = await vm.GetValues(); //.ConfigureAwait(false); Device.BeginInvokeOnMainThread(async () => { Debug.WriteLine("OpenPasswordDialog: starting PopModalAsync()"); await MainPage.Navigation.PopModalAsync(); Debug.WriteLine("OpenPasswordDialog: PopModalAsync() finished"); } ); return values; }
It works on the iPad. But on the iPhone simulator the app crashes and leaves this in my output window:
[0:] RoyalTSi[7894:562070] OpenPasswordDialog: starting PopModalAsync() [0:] [0:] RoyalTSi[7894:562070] System.NullReferenceException: Object reference not set to an instance of an object at Xamarin.Forms.Platform.iOS.Platform.DidAppear () [0x0000d] in <filename unknown>:0 at Xamarin.Forms.Platform.iOS.PlatformRenderer.ViewDidAppear (Boolean animated) [0x00006] in <filename unknown>:0 at (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr) at UIKit.UIApplication.Main (System.String[] args, IntPtr principal, IntPtr delegate) [0x00005] in /Users/builder/data/lanes/1962/8b265d64/source/maccore/src/UIKit/UIApplication.cs:63 at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x00038] in /Users/builder/data/lanes/1962/8b265d64/source/maccore/src/UIKit/UIApplication.cs:47 at RoyalMobileApps.XF.iOS.Application.Main (System.String[] args) [0x00002] in d:\RoyalFamily\RoyalMobileApps.XF\iOS\RoyalTSi\Main.cs:19
As the text "starting PopModalAsync()" is there and "PopModalAsync() finished" is not, I do know, which line produced the exception even though the stack trace does not list it.
It looks like the method works, when I call it from the UI thread. But when I call it from a background thread, then it crashes. I tried with and without the ConfigureAwait on the GetValues call - no difference.
How do I have to write that method?
I tried to create a repro project but failed. It always worked in that project.
So the error did not arise from what I told you.
The problem was, that I replaced the MainPage when OpenPasswordDialog returned. But as PopModalAsync is started on a different thread, OpenPasswordDialog returns before PopModalAsync was finished. As the latter needs a few hundred milliseconds to complete the animation, they seemed to always run at the same time.
Now I only replace the MainPage on an iPad (NavigationPage -> MasterDetailPage). On the iPhone I keep the same instance of NavigationPage and just replace the page it shows. I still need to test this a bit more, but it seems to work.
Thank you for your help!
Answers
@MichaelRumpler
How about something like this?
in your PasswordFormView
on ok button
I would submit this as a bug. NRE is something that should not happen. The only thing I'm not sure is modal navigation via MainPage only is correct.
I had something similar recently and used a message listener of type ModalCloseMsg in the main navigationpage constuctor to take care of closing modal dialogs. Seems to do the trick.
`
public partial class StartPage
{
public StartPage()
{
InitializeComponent();
`
@Xami3
I changed it now to this code:
And the PasswordFormViewModel looks like this:
I also tried various variants between the two, however, I still get that NRE.
Yes, @ylemsoul I will file a bug for this. It works on the iPad simulator and crashes on the iPhone 4s simulator. This cannot be intended. However, I do need a workaround.
@NMackay your solution also involves the MessagingCenter, or did I miss anything which distinguishes it from the first answer?
@MichaelRumpler
Not really, I was just reinforcing the point that I have tried various solutions and the only reliable way I could close modals was by having a message listener in the main navigation page to handle it. It works reliably both on iOS simulator and hardware (well iPhone4s, iPhone5s and 6 tested).
@MichaelRumpler
hmmm interesting, do you have a test project ( Either containing just two pages and a vm with one property or something similar )?
I tried to create a repro project but failed. It always worked in that project.
So the error did not arise from what I told you.
The problem was, that I replaced the MainPage when OpenPasswordDialog returned. But as PopModalAsync is started on a different thread, OpenPasswordDialog returns before PopModalAsync was finished. As the latter needs a few hundred milliseconds to complete the animation, they seemed to always run at the same time.
Now I only replace the MainPage on an iPad (NavigationPage -> MasterDetailPage). On the iPhone I keep the same instance of NavigationPage and just replace the page it shows. I still need to test this a bit more, but it seems to work.
Thank you for your help!
@MichaelRumpler
That's good news
For anyone else that stumbles on this. It sounds like a case of
https://bugzilla.xamarin.com/show_bug.cgi?id=40911 and or
https://bugzilla.xamarin.com/show_bug.cgi?id=42214
These both run into this issue though they couch it around Facebook.
Personnally got this multiple times using different custom renderers in iOS, Modal Dialogs etc. and thought I'd post up the bug numbers in case anyone wants to track it. 40911 contains a work around which is what we eventually did as well to get around it.
Looks like they think its fixed in 2.3.4