fabulous-dev / Fabulous

Declarative UI framework for cross-platform mobile & desktop apps, using MVU and F# functional programming
https://fabulous.dev
Apache License 2.0
1.13k stars 122 forks source link

FabulousContacts v2 Android breaks with DisplayAlert on the main thread #946

Closed kevkov closed 2 years ago

kevkov commented 2 years ago

Description

FabulousContacts' attempt to display an alert on the main thread with DisplayAlert throws an exception and stops message processing. The problem appears to be with how execution on the main thread is done.

Steps to reproduce

  1. Install FabulousContacts on the (Windows) Android emulator.
  2. Select the + toolbar button to add a contact.
  3. Select the SAVE toolbar button.

Expected behaviour

A data validation alert is shown.

Actual behaviour

No alert is shown. The app stops responding to user input, except the back button. Logcat shows an exception stacktrace:

Error: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Java.Lang.RuntimeException: Can't create handler inside thread Thread[Thread-18,10,main] that has not called Looper.prepare()
   at Java.Interop.JniEnvironment+InstanceMethods.CallObjectMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0006e] in <b3906e85894d452198b2cdafe195b6db>:0 
   at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualObjectMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0002a] in <b3906e85894d452198b2cdafe195b6db>:0 
   at AndroidX.AppCompat.App.AlertDialog+Builder.Create () [0x0000a] in <ef500be8070941d1b4a1f6f487dcdf4e>:0 
   at Xamarin.Forms.Platform.Android.PopupManager+PopupRequestHelper+DialogBuilder.Create () [0x00008] in <a62e492d2a9c482aa07a5a905a0d7044>:0 
   at Xamarin.Forms.Platform.Android.PopupManager+PopupRequestHelper.OnAlertRequested (Xamarin.Forms.Page sender, Xamarin.Forms.Internals.AlertArguments arguments) [0x00028] in <a62e492d2a9c482aa07a5a905a0d7044>:0 
     at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
   at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <ac5dda0190f24d829c27844a2bccc1dd>:0 
   --- End of inner exception stack trace ---
   at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00086] in <ac5dda0190f24d829c27844a2bccc1dd>:0 
   at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <ac5dda0190f24d829c27844a2bccc1dd>:0 
   at Xamarin.Forms.MessagingCenter+Subscription.InvokeCallback (System.Object sender, System.Object args) [0x00076] in <62e3629c74b84e3d834046331d2bb5f8>:0 
   at Xamarin.Forms.MessagingCenter.InnerSend (System.String message, System.Type senderType, System.Type argType, System.Object sender, System.Object args) [0x0006b] in <62e3629c74b84e3d834046331d2bb5f8>:0 
   at Xamarin.Forms.MessagingCenter.Xamarin.Forms.IMessagingCenter.Send[TSender,TArgs] (TSender sender, System.String message, TArgs args) [0x00013] in <62e3629c74b84e3d834046331d2bb5f8>:0 
   at Xamarin.Forms.MessagingCenter.Send[TSender,TArgs] (TSender sender, System.String message, TArgs args) [0x00005] in <62e3629c74b84e3d834046331d2bb5f8>:0 
   at Xamarin.Forms.Page.DisplayAlert (System.String title, System.String message, System.String accept, System.String cancel, Xamarin.Forms.FlowDirection flowDirection) [0x00046] in <62e3629c74b84e3d834046331d2bb5f8>:0 
   at Xamarin.Forms.Page.DisplayAlert (System.String title, System.String message, System.String cancel) [0x00000] in <62e3629c74b84e3d834046331d2bb5f8>:0 
   at FabulousContacts.Helpers.displayAlert (System.String title, System.String message, System.String cancel) [0x0000a] in <2c5b101886e8bbcecfa0f86a9cda9d4b>:0 
   at FabulousContacts.EditPage+sayContactNotValidAsync@133.Invoke (Microsoft.FSharp.Core.Unit unitVar) [0x00016] in <2c5b101886e8bbcecfa0f86a9cda9d4b>:0 
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult] (Microsoft.FSharp.Control.AsyncActivation`1[T] ctxt, TResult result1, Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] part2) [0x00006] in <91a8467f9620cad2b0f7b10b655a286c>:0 
   at FabulousContacts.EditPage+trySaveAsync@155-3.Invoke (Microsoft.FSharp.Control.AsyncActivation`1[T] ctxt) [0x00000] in <2c5b101886e8bbcecfa0f86a9cda9d4b>:0 

The TicTacToe app displays an alert when the game completes and does not suffer this issue. In this case the alert is displayed on the main thread using Application.Current.Dispatcher.BeginInvokeOnMainThread. FabulousContacts displays the alert on the main thread using Device.InvokeOnMainThreadAsync.

kevkov commented 2 years ago

Possibly the main thread bit is a red herring. Investigating further.

kevkov commented 2 years ago

It appears getting DisplayAlert to work correctly on Android is dependent on how main thread execution is is setup and how the async computation is performed. Don't really understand why but documented it with this app: https://github.com/kevkov/FabDisplayAlert

image

Not tested on iOS yet.