XJonOneX
XJonOneX

Reputation: 424

Why is .NET MAUI Dependency Injection crashing my app?

I'm trying to follow this article on .NET MAUI dependency injection.

My MauiProgram.cs

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        builder.Services.AddSingleton<IDataService, DataService>();
        builder.Services.AddTransient<NavigationService>();
        builder.Services.AddTransient<ValidationService>();

        builder.Services.AddSingleton<BudgetViewPage>();
        builder.Services.AddSingleton<BudgetViewModel>();
        builder.Services.AddSingleton<AccountsViewModel>();

        builder.Services.AddSingleton<FlyoutMenuRoot>();

        return builder.Build();
    }
}

My App.xaml.cs

public partial class App : Application
{
    public App(FlyoutMenuRoot flyoutMenuRoot)
    {
        InitializeComponent();
        MainPage = flyoutMenuRoot;
    }
}

My FlyoutMenuRoot.xaml.cs

public partial class FlyoutMenuRoot : FlyoutPage
{
    IDataService dataService;
    BudgetViewModel budgetViewModel;

    private NavigationService NavigationService = new();
    public FlyoutMenuRoot(IDataService dataService, BudgetViewModel budgetViewModel)
    {
        InitializeComponent();
        this.dataService = dataService;
        this.budgetViewModel = budgetViewModel;
        Detail = new NavigationPage(new BudgetViewPage(budgetViewModel));
        flyoutMenuRoot.flyoutCollectionView.SelectionChanged += OnSelectionChanged;
    }

    void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var item = e.CurrentSelection.FirstOrDefault() as FlyoutPageItem;

        if(item != null)
        {
            if(item.TargetType == typeof(SelectAccountPage))
            {
                NavigationService.PushToStack((Page)Activator.CreateInstance(item.TargetType, new AccountsViewModel(dataService, budgetViewModel)));
            }
            else
            {
                NavigationService.PushToStack((Page)Activator.CreateInstance(item.TargetType));
            }
            
            this.IsPresented = false;

            flyoutMenuRoot.flyoutCollectionView.SelectedItem = null;
        }
    }
}

Based on the linked article, this should work, but my app crashes on the splash screen.

If my App.xaml.cs is this:

public partial class App : Application
{
    public App()
    {
        InitializeComponent();
        DataService dataService = new();
        BudgetViewModel budgetViewModel = new(dataService);
        MainPage = new FlyoutMenuRoot(dataService, budgetViewModel);
    }
}

Then it works with no problem.

My understanding is that you shouldn't have to new() up an instance of your classes with Dependency Injection, that the container will do it automatically for you based on what's listed in the constructor. I'm following the article, so why is it crashing?

Edit:

I stepped through the code and narrowed the crash down to the InitializeComponent() call under FlyoutMenuPage()

public partial class FlyoutMenuPage : ContentPage
{
    public FlyoutMenuPage()
    {
        try
        { 
            InitializeComponent();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

The message written to the output window is: [DOTNET] Position 11:5. StaticResource not found for key Primary

That's referencing this line in the FlyoutMenuPage.xaml BackgroundColor="{StaticResource Primary}" This is confounding because that line never threw an exception until I tried following the method for DI from the article. If I go back to constructor injection, it doesn't crash.

Upvotes: 0

Views: 1089

Answers (2)

XJonOneX
XJonOneX

Reputation: 424

So I'm experiencing a logical issue. Take this App for example:

public partial class App : Application
{
    public App(MainPage mp, MainPageViewModel vm)
    {
        InitializeComponent();

        MainPage = mp; // new MainPage(); //AppShell();
        MainPage.BindingContext= vm;
    }
}

And in the App.xaml, we have this:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MVVMDI"
             x:Class="MVVMDI.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Because I have MainPage in my constructor, ready for dependency injection, the xaml in MainPage gets loaded up before the xaml in App. So, with that in mind, App.xaml hasn't yet registered the Colors.xaml because InitializeComponent hasn't been called yet. So because MainPage is trying to reference StaticResource Primary, which is in Colors.xaml, which hasn't been registered yet, I get my exception.

This answers my question as to why DI is making my app crash.

(So to solve this, I just need to find a way to register Colors.xaml application-wide like App does....)

Upvotes: 0

H.A.H.
H.A.H.

Reputation: 3917

First.

DI is there for you, so you don't have to construct your classes manually. (As I told you yesterday) You add classes as Singletons, and at the same time, you are manually constructing them. DI will call those constructions when needed. Your idea that they are "never initialized" and you have to do it at least once is wrong.

Second.

DI is not "crashing" your app. If anything, not injecting services and/or ViewModels cause runtime exceptions. (When you try to navigate to something, that uses such Type in its constructor for example.) Not the oposite.

Third.

DI has very little to do with XAML/Resources problems. Especially with your BackgroundColor problem.

Upvotes: 0

Related Questions