Nico Sz
Nico Sz

Reputation: 51

InvalidCastException when using MAUI Shell navigation and passing an object to page

When I try navigating to the page without passing an argument, everything works as expected. As soon as I pass a Dictionary into the Method I get a weird exception with "The application is in break mode Your app has entered a break state, but there is no code to show because all threads were executing external code (typically system or framework code)."

Command that executes the navigation:

[RelayCommand]
async Task OpenEntry(Training training)
{
    //This navigates to page
    //await Shell.Current.GoToAsync(nameof(DetailPage));
    //This doesnt navigate to page and throws: System.InvalidCastException: 'Object must implement IConvertible.'
    await Shell.Current.GoToAsync(
        nameof(DetailPage), 
        true, 
        new Dictionary<string, object>() 
        { 
            { "DetailTraining", (Training)training } 
        });
}

Viewmodel of page I want to navigate to:

[QueryProperty(nameof(DetailedTraining),"DetailTraining")]
public partial class DetailViewModel : ObservableObject
{
    [ObservableProperty]
    Training detailedTraining;

    public DetailViewModel()
    {}
}

Callstack of exception:

0xFFFFFFFFFFFFFFFF in Android.Runtime.JNIEnv.monodroid_debugger_unhandled_exception C#
0x1A in Android.Runtime.JNINativeWrapper._unhandled_exception at /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:12,5 C#
0x1D in Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V at /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:23,26    C#
0x17 in System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw    C#
0x6 in System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0 C#
0xC in Android.App.SyncContext. at /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:36,19 C#
0xE in Java.Lang.Thread.RunnableImplementor.Run at /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:36,6 C#
0x8 in Java.Lang.IRunnableInvoker.n_Run at /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net7.0/android-33/mcw/Java.Lang.IRunnable.cs:84,4    C#
0x8 in Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V at /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:22,5  C#

As I said, I think I have the Routing and the Dependencies set up correctly because simple navigation without passing an object works just fine.

I have also tried passing a string to the page like this:

await Shell.Current.GoToAsync($"{nameof(DetailPage)}?Text={training.Name}");

Then I adjusted the QueryProperty: [QueryProperty("Text","Text")] I also added to the viewmodel: [ObservableProperty] text; When I tried passing a string like this I had no problem and everything worked as expected.

But as soon as I try using the overload public Task GoToAsync(ShellNavigationState state, IDictionary<string, object> parameters); the application crashes and Visual Studio 2022 shows me the "Your application is in break mode" screen and a exception popup with "System.InvalidCastException: 'Object must implement IConvertible.'".

All of this is happening when I run it on the Android simulator. When I try running it on Windows, it breaks in the "App.g.i.cs" (Autogenerated file) in this if clause:

#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
    UnhandledException += (sender, e) =>
    {
        if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
    };
#endif

Also tried the hack of @ToolmakerSteve from this post: Maui shell navigation fails - rare reproduction but this just makes the windows variant also immediately show me the "System.InvalidCastException" popup instead of breaking in the auto generated file Thanks in Advance!

Upvotes: 2

Views: 1716

Answers (1)

Julian
Julian

Reputation: 8883

You're mixing up string-based query parameters and object-based navigation parameters.

String-based query parameters

You can pass the training object as a query parameter, provided that it is a string:

await Shell.Current.GoToAsync(
    $"{nameof(DetailPage)}?DetailTraining={training}&Duration={10}",
    true);

In your ViewModel or Page, you can then use the [QueryProperty] attribute to receive the properties:

[QueryProperty(nameof(DetailedTraining),"DetailTraining")]
[QueryProperty(nameof(Duration), nameof(Duration)]
public partial class DetailViewModel : ObservableObject
{
    [ObservableProperty]
    string detailedTraining;

    [ObservableProperty]
    int duration;
}

This uses the string-based query parameters approach.

Note: This only works with simple value types like string, int and bool.

Object-based navigation parameters

If you want to use the object-based navigation parameters approach to pass objects of your own classes and types, you'll need to implement the IQueryAttributable interface on your receiving class, e.g. your ViewModel:

public partial class DetailViewModel : ObservableObject, IQueryAttributable
{
    [ObservableProperty]
    Training detailedTraining;

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        DetailedTraining = query["DetailTraining"] as Training;
    }
}

Upvotes: 4

Related Questions