mauimaui-androidmaui-ios

.NET MAUI Crashes on PROD


I'm currently developing a .NET MAUI app, everything works fine on DEBUG mode but when publishing the app I'm getting:

System.InvalidCastException
Arg_InvalidCastException

exception on launch making iOS and Android apps unusable.

According to stack trace it's something related to the constructor of my pages,

EDIT [A better stack trace]

2023-02-22 09:27:14.000039-0600 Envivu[94104:520252] Unhandled managed exception: Arg_InvalidCastException (System.InvalidCastException)
   at Envivu.Buyer.Controls.Views.OnboardingItemView.InitializeComponent()
   at Envivu.Buyer.Controls.Views.OnboardingItemView..ctor()
   at Envivu.Buyer.Pages.OnboardingPage.InitializeComponent()
   at Envivu.Buyer.Pages.OnboardingPage..ctor(OnboardingVm vm)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Maui.MauiContext.WrappedServiceProvider.GetService(Type serviceType)
   at Microsoft.Maui.Controls.ShellContent.<>c__DisplayClass19_0.<Microsoft.Maui.Controls.IShellContentController.GetOrCreateContent>b__0()
   at Microsoft.Maui.Controls.ElementTemplate.CreateContent()
   at Microsoft.Maui.Controls.Internals.DataTemplateExtensions.CreateContent(DataTemplate self, Object item, BindableObject container)
   at Microsoft.Maui.Controls.ShellContent.Microsoft.Maui.Controls.IShellContentController.GetOrCreateContent()
   at Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.LoadRenderers()
   at Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.ViewDidLoad()
--- End of stack trace from previous location ---
   at UIKit.UIApplication.UIApplicationMain(Int32 argc, String[] argv, IntPtr principalClassName, IntPtr delegateClassName)
   at UIKit.UIApplication.Main(String[] args, Type principalClass, Type delegateClass)
   at Envivu.Buyer.Program.Main(String[] args)

but that View Model is correctly registered on AppShell and Maui App builder.

 builder.Services.AddSingleton<AppShell>();

 builder.Services.AddPage<SplashScreenPage, SplashScreenVm>(shouldRegisterRoute: false);
 builder.Services.AddPage<OnboardingPage, OnboardingVm>(shouldRegisterRoute: false);

And With this AppShell is injected in App.xaml.cs

public App(AppShell appShell)
    {
        InitializeComponent();
        MainPage = appShell;
    }

I have a markup extension that makes a simple calculation to change the size of the view according to device density and screen height and width, I'm not sure if that has something to do with this, cause the animated splash screen (which uses that markup extension as well) is working fine.

[CODE] OnnoardingPage.xaml

<Grid Margin="0,10,0,0" RowSpacing="{mk:SizeAdapter DefaultSize=20, AttributeType=Height}">
        <Grid.RowDefinitions>
            <RowDefinition Height="{mk:SizeAdapter DefaultSize=316, AttributeType=Height}" />
            <RowDefinition Height="{mk:SizeAdapter DefaultSize=286, AttributeType=Height}" />
            <RowDefinition Height="{mk:SizeAdapter DefaultSize=10, AttributeType=Height}" />
            <RowDefinition Height="{mk:SizeAdapter DefaultSize=100, AttributeType=Height}" />
        </Grid.RowDefinitions>


        <Image
            Aspect="AspectFill"
            Grid.Row="0"
            HeightRequest="{mk:SizeAdapter DefaultSize=316,
                                           AttributeType=Height}"
            Source="onboard_image_0.png" />

        <Grid ColumnSpacing="{mk:SizeAdapter DefaultSize=10, AttributeType=Width}" Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="{mk:SizeAdapter DefaultSize=20, AttributeType=Width}" />
                <ColumnDefinition Width="{mk:SizeAdapter DefaultSize=315, AttributeType=Width}" />
                <ColumnDefinition Width="{mk:SizeAdapter DefaultSize=20, AttributeType=Width}" />
            </Grid.ColumnDefinitions>

            <Border
                Background="{StaticResource White}"
                Grid.Column="0"
                HeightRequest="{mk:SizeAdapter DefaultSize=222,
                                               AttributeType=Height}"
                StrokeShape="RoundRectangle 0,20,0,20" />

            <custom:AnimatedStepper Grid.Column="1"
                                    CurrentIndex="{Binding CurrentIndex}" >
                <views:OnboardingItemView
                    ButtonText="Continuar"
                    SectionMessage="BLAH"
                    SectionTitle="BLAH TITLE" />

                <views:OnboardingItemView
                    ButtonText="Continuar"
                    SectionMessage="BLAH"
                    SectionTitle="BLAH TITLE" />

                <views:OnboardingItemView
                    ButtonText="Empezar"
                    SectionMessage="BLAH"
                    SectionTitle="BLAH TITLE" />
            </custom:AnimatedStepper>

            <Border
                Background="{StaticResource White}"
                Grid.Column="2"
                HeightRequest="{mk:SizeAdapter DefaultSize=222,
                                               AttributeType=Height}"
                StrokeShape="RoundRectangle 20,0,20,0" />

        </Grid>

        <VerticalStackLayout Grid.Row="2" Spacing="20">
            <IndicatorView HorizontalOptions="CenterAndExpand" x:Name="OnboardingIndicator">
                <IndicatorView.IndicatorTemplate>
                    <DataTemplate>
                        <BoxView CornerRadius="4" HeightRequest="8" />
                    </DataTemplate>
                </IndicatorView.IndicatorTemplate>
            </IndicatorView>

            <Button
                BackgroundColor="{StaticResource Senary}"
                Command="{Binding ForwardCommand}"
                HorizontalOptions="Center"
                Text="Saltar"
                TextColor="{StaticResource White}"
                VerticalOptions="Center"
                WidthRequest="{mk:SizeAdapter DefaultSize=120,
                                              AttributeType=Width}" />
        </VerticalStackLayout>
    </Grid>

OnboardinVm.cs

public partial class OnboardingVm : BaseViewModel
{
    private object _lock = new object();

    [ObservableProperty] private int _currentIndex;

    public OnboardingVm(
        INavigationService navigation,
        ISessionService session,
        IIdentityService identity,
        ILoggerService logger,
        IAlertService notification)
        : base(navigation, session, identity, logger, notification)
    {

    }

    [RelayCommand]
    private Task GoNext()
    {
        lock (_lock)
        {
            if (CurrentIndex >= 2) return Forward();
            CurrentIndex++;
            return Task.CompletedTask;
        }
    }

    [RelayCommand]
    private Task Forward()
    {
        Session.HasPassedOnboarding = true;
        return Navigation.NavigateToRoot("LoginSelection");
    }
}

OnboardingItemView.xaml

<Grid>
        <Border Background="{StaticResource White}" Stroke="{StaticResource White}">
            <Border.StrokeShape>
                <RoundRectangle CornerRadius="{mk:SizeAdapter DefaultSize=20, AttributeType=BorderRadius}" />
            </Border.StrokeShape>
        </Border>

        <Grid Margin="20" RowSpacing="{mk:SizeAdapter DefaultSize=10, AttributeType=Height}">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="{mk:SizeAdapter DefaultSize=50, AttributeType=Height}" />
            </Grid.RowDefinitions>

            <Label
                FontAttributes="Bold"
                FontSize="{mk:SizeAdapter DefaultSize=30,
                                          AttributeType=Height}"
                Grid.Row="0"
                HorizontalTextAlignment="Center"
                Text="{Binding SectionTitle, Source={x:Reference OnboardItem}}"
                TextColor="{StaticResource Black}" />
            <Label
                FontSize="{mk:SizeAdapter DefaultSize=18,
                                          AttributeType=Height}"
                Grid.Row="1"
                HorizontalTextAlignment="Center"
                Text="{Binding SectionMessage, Source={x:Reference OnboardItem}}"
                TextColor="{StaticResource Black}" />
            <Button
                BackgroundColor="{StaticResource Primary}"
                Command="{Binding GoNextCommand}"
                Grid.Row="2"
                Style="{StaticResource NormalButton}"
                Text="{Binding ButtonText, Source={x:Reference OnboardItem}}"
                TextColor="{StaticResource White}"
                WidthRequest="{mk:SizeAdapter DefaultSize=238,
                                              AttributeType=Width}" />
        </Grid>

    </Grid>

OnboardingItemView.xaml.cs

public partial class OnboardingItemView : ContentView
{

    public static readonly BindableProperty SectionTitleProperty =
            BindableProperty.Create(nameof(SectionTitle), typeof(string), typeof(OnboardingItemView), string.Empty);

    public string SectionTitle
    {
        get => (string)GetValue(SectionTitleProperty);
        set => SetValue(SectionTitleProperty, value);
    }

    public static readonly BindableProperty SectionMessageProperty =
            BindableProperty.Create(nameof(SectionMessage), typeof(string), typeof(OnboardingItemView), string.Empty);

    public string SectionMessage
    {
        get => (string)GetValue(SectionMessageProperty);
        set => SetValue(SectionMessageProperty, value);
    }

    public static readonly BindableProperty ButtonTextProperty =
            BindableProperty.Create(nameof(ButtonText), typeof(string), typeof(OnboardingItemView), string.Empty);

    public string ButtonText
    {
        get => (string)GetValue(ButtonTextProperty);
        set => SetValue(ButtonTextProperty, value);
    }

    public OnboardingItemView()
    {
        InitializeComponent();
    }
}

This is an example of how MarkupExtension Size Adapter works for Height:

private double GetProportionateScreenHeight(double inputHeight)
    {
        var displayInfo = DeviceDisplay.MainDisplayInfo;

        var displayHeight = displayInfo.Height / displayInfo.Density;

        var rawNewHeight = (inputHeight / 812.0) * displayHeight;

        return Math.Round(rawNewHeight,0);
    }

Any idea would be much appreciated.


Solution

  • Well after a whole day debugging this, ended up being the cast of the properties

    let me explain further:

    On debug mode I can assign a double to the grid length value, but when app is on release mode a new GridLenght(value) is needed so that can be casted to the Bindable property. BUT this cast doesn't work on debug... so it's quite troublesome ... ended up setting a bunch of #if statements to make this work properly.

    Hope this helps someone in that get's in the same trouble as I did.