dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.14k stars 1.74k forks source link

[Android] Exception after changing MainPage on App.xaml.cs from ContentPage to AppShell on runtime. #21257

Closed orlandommb closed 7 months ago

orlandommb commented 7 months ago

Description

My app is ported from xamarin.forms, where the same workflow works, i set on app.xaml.cs contructor MainPage as LoginPage wich is a content page where the user can login, after login app sets MainPage as AppShell.

Exception happens on OnStart() where i check if user is authenticated and based on this its changes MainPage from LoginPage to AppShell if its :

protected override void OnStart()
{
    Task.Run(IsAuthenticated);
}

private async Task IsAuthenticated()
{
    try
    {
        var user = await SecureStorage.Default.GetAsync("Usuario");
        if (!string.IsNullOrWhiteSpace(user))
        {
            var Usuario = JsonSerializer.Deserialize<UsuarioDTO>(user);

            if (IsTokenExpired(Usuario) is false)
            {
                MainPage = new AppShell();
            }
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
}

[alphasoftmobile] Invalid ID 0x00000001. [AndroidRuntime] Shutting down VM [AndroidRuntime] FATAL EXCEPTION: main [AndroidRuntime] Process: com.companyname., PID: 12526 [AndroidRuntime] java.lang.IllegalArgumentException: No view found for id 0x1 (unknown) for fragment ShellItemRenderer{aefcfce} (42ca5f5d-ecbb-4755-8799-c3bcf3286579 id=0x1) [AndroidRuntime] at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:547) [AndroidRuntime] at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272) [AndroidRuntime] at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1943) [AndroidRuntime] at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1845) [AndroidRuntime] at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1782) [AndroidRuntime] at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:565) [AndroidRuntime] at android.os.Handler.handleCallback(Handler.java:942) [AndroidRuntime] at android.os.Handler.dispatchMessage(Handler.java:99) [AndroidRuntime] at android.os.Looper.loopOnce(Looper.java:201) [AndroidRuntime] at android.os.Looper.loop(Looper.java:288) [AndroidRuntime] at android.app.ActivityThread.main(ActivityThread.java:7872) [AndroidRuntime] at java.lang.reflect.Method.invoke(Native Method) [AndroidRuntime] at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) [AndroidRuntime] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Java.Lang.IllegalArgumentException: 'No view found for id 0x1 (unknown) for fragment ShellItemRenderer{aefcfce} (42ca5f5d-ecbb-4755-8799-c3bcf3286579 id=0x1)'

Steps to Reproduce

No response

Link to public reproduction project repository

No response

Version with bug

8.0.10 SR3

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Android 13

Did you find any workaround?

No response

Relevant log output

No response

PureWeen commented 7 months ago

Can you test with the latest nightly build? https://github.com/dotnet/maui/wiki/Nightly-Builds

This should also be fixed on SR3 (8.0.10)

orlandommb commented 7 months ago

@PureWeen heres a video of the exception with the most recent nightly build :

https://github.com/dotnet/maui/assets/12052916/387e173a-ce85-451f-9fb2-56c1ec7118b6

and heres the stack trace:

[MonoDroid] Java.Lang.IllegalArgumentException: No view found for id 0x1 (unknown) for fragment ShellItemRenderer{8d61a9a} (8e18cdee-06d3-40a2-9cc9-40c9ea9c4773 id=0x1) [MonoDroid] [MonoDroid] --- End of managed Java.Lang.IllegalArgumentException stack trace --- [MonoDroid] java.lang.IllegalArgumentException: No view found for id 0x1 (unknown) for fragment ShellItemRenderer{8d61a9a} (8e18cdee-06d3-40a2-9cc9-40c9ea9c4773 id=0x1) [MonoDroid] at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:547) [MonoDroid] at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272) [MonoDroid] at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1943) [MonoDroid] at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1845) [MonoDroid] at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1782) [MonoDroid] at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:565) [MonoDroid] at android.os.Handler.handleCallback(Handler.java:942) [MonoDroid] at android.os.Handler.dispatchMessage(Handler.java:99) [0:] Java.Lang.RuntimeException: Can't create handler inside thread Thread[Thread-15,10,main] that has not called Looper.prepare() at Java.Interop.JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod(JniObjectReference instance, JniObjectReference type, JniMethodInfo method, JniArgumentValue args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 20830 at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 75 at Android.App.Activity.SetContentView(View view) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Android.App.Activity.cs:line 6340 at Microsoft.Maui.Handlers.WindowHandler.MapContent(IWindowHandler handler, IWindow window) in D:\a_work\1\s\src\Core\src\Handlers\Window\WindowHandler.Android.cs:line 28 at Microsoft.Maui.PropertyMapper`2.<>c__DisplayClass5_0[[Microsoft.Maui.IWindow, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.Handlers.IWindowHandler, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].b__0(IElementHandler h, IElement v) in D:\a_work\1\s\src\Core\src\PropertyMapper.cs:line 172 at Microsoft.Maui.PropertyMapper.UpdatePropertyCore(String key, IElementHandler viewHandler, IElement virtualView) in D:\a_work\1\s\src\Core\src\PropertyMapper.cs:line 47 at Microsoft.Maui.PropertyMapper.UpdateProperty(IElementHandler viewHandler, IElement virtualView, String property) in D:\a_work\1\s\src\Core\src\PropertyMapper.cs:line 72 at Microsoft.Maui.Handlers.ElementHandler.UpdateValue(String property) in D:\a_work\1\s\src\Core\src\Handlers\Element\ElementHandler.cs:line 87 at Microsoft.Maui.Controls.Window.OnPropertyChanged(String propertyName) in D:\a_work\1\s\src\Controls\src\Core\Window\Window.cs:line 288 at Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty property, BindablePropertyContext context, Object value, Boolean currentlyApplying, SetValueFlags attributes, SetterSpecificity specificity, Boolean silent) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 641 at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes, SetterSpecificity specificity) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 569 at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value) in D:\a_work\1\s\src\Controls\src\Core\BindableObject.cs:line 474 at Microsoft.Maui.Controls.Window.set_Page(Page value) in D:\a_work\1\s\src\Controls\src\Core\Window\Window.cs:line 114 at Microsoft.Maui.Controls.Application.set_MainPage(Page value) in D:\a_work\1\s\src\Controls\src\Core\Application\Application.cs:line 106 at AlphaSoftMobile.App.IsAuthenticated() in C:\Users\Admin\Documents\GitHub\AlphaSoftPOS\AlphaSoftMobile\App.xaml.cs:line 80 --- End of managed Java.Lang.RuntimeException stack trace --- java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-15,10,main] that has not called Looper.prepare() at android.os.Handler.(Handler.java:227) at android.os.Handler.(Handler.java:129) at android.view.View$VisibilityChangeForAutofillHandler.(View.java:29670) at android.view.View$VisibilityChangeForAutofillHandler.(Unknown Source:0) at android.view.View.onVisibilityAggregated(View.java:15476) at android.widget.TextView.onVisibilityAggregated(TextView.java:11357) at android.view.View.dispatchDetachedFromWindow(View.java:21340) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:5785) at android.view.ViewGroup.removeAllViews(ViewGroup.java:5731) at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:765) at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:203)

--- End of managed Java.Lang.RuntimeException stack trace --- java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-15,10,main] that has not called Looper.prepare() at android.os.Handler.(Handler.java:227) at android.os.Handler.(Handler.java:129) at android.view.View$VisibilityChangeForAutofillHandler.(View.java:29670) at android.view.View$VisibilityChangeForAutofillHandler.(Unknown Source:0) at android.view.View.onVisibilityAggregated(View.java:15476) at android.widget.TextView.onVisibilityAggregated(TextView.java:11357) at android.view.View.dispatchDetachedFromWindow(View.java:21340) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3949) at android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:5785) at android.view.ViewGroup.removeAllViews(ViewGroup.java:5731) at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:765) at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:203) [MonoDroid] at android.os.Looper.loopOnce(Looper.java:201) [MonoDroid] at android.os.Looper.loop(Looper.java:288) [MonoDroid] at android.app.ActivityThread.main(ActivityThread.java:7872) [MonoDroid] at java.lang.reflect.Method.invoke(Native Method) [MonoDroid] at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) [MonoDroid] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) [MonoDroid] [MonoDroid] --- End of managed Java.Lang.IllegalArgumentException stack trace --- [MonoDroid] java.lang.IllegalArgumentException: No view found for id 0x1 (unknown) for fragment ShellItemRenderer{8d61a9a} (8e18cdee-06d3-40a2-9cc9-40c9ea9c4773 id=0x1) [MonoDroid] at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:547) [MonoDroid] at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272) [MonoDroid] at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1943) [MonoDroid] at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1845) [MonoDroid] at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1782) [MonoDroid] at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:565) [MonoDroid] at android.os.Handler.handleCallback(Handler.java:942) [MonoDroid] at android.os.Handler.dispatchMessage(Handler.java:99) [MonoDroid] at android.os.Looper.loopOnce(Looper.java:201) [MonoDroid] at android.os.Looper.loop(Looper.java:288) [MonoDroid] at android.app.ActivityThread.main(ActivityThread.java:7872) [MonoDroid] at java.lang.reflect.Method.invoke(Native Method) [MonoDroid] at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) [MonoDroid] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) [MonoDroid]

orlandommb commented 7 months ago

Hi @PureWeen ! Small repro here: RepoMainPageSetBugMauiApp.zip

Steps to reproduce:

  1. Create a "Login" ContentPage.
  2. Add a Login Button and Click Event.
  3. Inside the click event of the button using SecureStorage.Default set any kind of value you want that simulates a authenticated user and then set MainPage as AppShell simulating a successful login.
  4. Set LoginPage as MainPage.
  5. On App.xaml.cs override OnStart method and using SecureStorage.Default.GetAsync() get the value that simulates the authenticated user and if the value is null set MainPage as LoginPage if its not set MainPage as AppShell
  6. Build and click login to set the value on secure storage close the app.
  7. Build again and wait for it to execute OnStart, since the value is already set it will set MainPage as AppShell this i where the exception occurs.
Px7-941 commented 7 months ago

I think it has something to do with the ui thread. java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-15,10,main] that has not called Looper.prepare() https://stackoverflow.com/questions/3875184/cant-create-handler-inside-thread-that-has-not-called-looper-prepare

Try this as a workaround until a definitive fix is found.

this.Dispatcher.Dispatch(()=>
{
        MainPage = new MyPageOrAppShell();
});

@orlandommb I tested it in your code with this one and it worked.

private async Task IsAuthenticated()
{
    var User = await SecureStorage.Default.GetAsync("User");
    this.Dispatcher.Dispatch(()=>
    {
        if (User is null)
        {
            MainPage = new LoginPage();
        }
        else
        {
            MainPage = new AppShell();
        }
    });
}

Thank you for the test project, we have had this error for months. We didn't know where to look. The error was also not reproducible for us until now. But the App Center logs prove the problem with thousands of reports. We also set the MainPage in an async method, we will probably also test this for ourselves with this ui thread fix.

orlandommb commented 7 months ago

Hi @Px7-941 ! In the process of making the test project it also came to mind it had something to do with the thread but it was a feeling, couldn't put myself to read the logs as i was traveling for work and im not a travel person so didn't have the energy for it. But im glad i could help with something !😁

orlandommb commented 7 months ago

@Px7-941 @PureWeen hi again ! @Px7-941 i've applied your workaround and it works, after that i've gotten to another issue that may be related or not, but i wanted to get you guy's opinion on it, my AppShell is binded to a ViewModel where is has a Command binded to OnAppearing method by using behaviors, in this method i try to get the Authenticated value set on the login page, but i get a System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. on the inner exception it says "System.InvalidOperationException: Sequence contains no matching element" on await SecureStorage.Default.GetAsync(), i've already checked that im using the sames key for the value. Could it be related to the thread? Should i open another issue for this ?

PureWeen commented 7 months ago

@Px7-941 @PureWeen hi again ! @Px7-941 i've applied your workaround and it works, after that i've gotten to another issue that may be related or not, but i wanted to get you guy's opinion on it, my AppShell is binded to a ViewModel where is has a Command binded to OnAppearing method by using behaviors, in this method i try to get the Authenticated value set on the login page, but i get a System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. on the inner exception it says "System.InvalidOperationException: Sequence contains no matching element" on await SecureStorage.Default.GetAsync(), i've already checked that im using the sames key for the value. Could it be related to the thread? Should i open another issue for this ?

Can you open a new issue? Can we close this one?

dotnet-policy-service[bot] commented 7 months ago

Hi @orlandommb. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

orlandommb commented 7 months ago

@Px7-941 @PureWeen hi again ! @Px7-941 i've applied your workaround and it works, after that i've gotten to another issue that may be related or not, but i wanted to get you guy's opinion on it, my AppShell is binded to a ViewModel where is has a Command binded to OnAppearing method by using behaviors, in this method i try to get the Authenticated value set on the login page, but i get a System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. on the inner exception it says "System.InvalidOperationException: Sequence contains no matching element" on await SecureStorage.Default.GetAsync(), i've already checked that im using the sames key for the value. Could it be related to the thread? Should i open another issue for this ?

Can you open a new issue?

Can we close this one?

@PureWeen Hi! The workaround worked but i don't think that's how is supposed to behave, so im leaving it up to you, to decide the issue can be closed.

PureWeen commented 7 months ago

I think this is all acting correctly

You're code here

protected override void OnStart()
{
    Task.Run(IsAuthenticated);
}

private async Task IsAuthenticated()
{
    try
    {
        var user = await SecureStorage.Default.GetAsync("Usuario");
        if (!string.IsNullOrWhiteSpace(user))
        {
            var Usuario = JsonSerializer.Deserialize<UsuarioDTO>(user);

            if (IsTokenExpired(Usuario) is false)
            {
                MainPage = new AppShell();
            }
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
}

Task.Run is going to cause your code to run to a task pool thread not the UI Thread. So once you want to start making UI changes then you have to get back to the UI Thread. Which is what the "Dispatch" call is doing.

You can just use the dispatcher instead of Task.Run I don't think Task.Run gets you anything here. The storage call is an async call so it's already going to relieve your UI Thread by design.

I think your code is doing double duty.