Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
davidortinau avatar

Maui Dependency Injection

  • 51 installs
  • 163 repo stars
  • Updated July 6, 2026
  • davidortinau/maui-skills

Covers dependency injection in .NET MAUI apps including service registration, lifetime selection, and constructor injection.

About

Guides dependency injection in .NET MAUI apps covering service registration, lifetime selection (Singleton/Transient/Scoped) and constructor injection. A developer uses it when structuring services and dependencies in a MAUI app.

  • Service registration and lifetime selection
  • Constructor injection patterns

Maui Dependency Injection by the numbers

  • 51 all-time installs (skills.sh)
  • Ranked #593 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davidortinau/maui-skills --skill maui-dependency-injection

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs51
repo stars163
Last updatedJuly 6, 2026
Repositorydavidortinau/maui-skills

What it does

Covers dependency injection in .NET MAUI apps including service registration, lifetime selection, and constructor injection.

Files

SKILL.mdMarkdownGitHub ↗

Dependency Injection in .NET MAUI

Lifetime Decision Framework

QuestionAnswer → Lifetime
Does it hold shared state or is expensive to create?AddSingleton
Is it stateless, lightweight, or per-request?AddTransient
Do you manage IServiceScope yourself?AddScoped
⚠️ Avoid `AddScoped` in MAUI — there is no built-in scope per page.
Using it without manually creating IServiceScope gives you singleton
behaviour silently, which is confusing and error-prone.

Singleton traps

// ❌ ViewModel registered as Singleton — stale data across navigations
builder.Services.AddSingleton<DetailViewModel>();

// ✅ ViewModels are Transient — fresh instance each navigation
builder.Services.AddTransient<DetailViewModel>();
Register Pages and ViewModels as Transient. Register **services that hold
shared state as Singleton** (e.g., IDataService, HttpClient factory).

---

Gotcha: XAML Resource Parsing vs. DI Timing

XAML resources (App.xaml styles, converters) are parsed during `InitializeComponent()` — before the DI container is fully available. If a resource or converter needs a service, resolve it in CreateWindow(), not in the constructor.

// ❌ Resolving services during XAML parse — container may not be ready
public App(IDataService data)
{
    InitializeComponent(); // XAML parses here
    _data = data;          // may fail for types not yet resolved
}

// ✅ Defer service resolution to CreateWindow
public partial class App : Application
{
    private readonly IServiceProvider _services;

    public App(IServiceProvider services)
    {
        _services = services;
        InitializeComponent();
    }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        // Safe — container is fully built
        var mainPage = _services.GetRequiredService<MainPage>();
        return new Window(new AppShell());
    }
}

---

Gotcha: Unregistered Page Silently Skips DI

If a Page is used in Shell XAML (<ShellContent ContentTemplate="...">) but not registered in builder.Services, MAUI instantiates it with the parameterless constructor. Dependencies are silently null — no exception.

// ❌ Page not registered — constructor injection silently skipped
// builder.Services.AddTransient<DetailPage>(); // missing!

// ✅ Always register pages that need injection
builder.Services.AddTransient<DetailPage>();
builder.Services.AddTransient<DetailViewModel>();

---

Anti-Pattern: Service Locator Overuse

// ❌ Service locator scattered through code — hard to test, hides dependencies
public void DoWork()
{
    var service = this.Handler.MauiContext.Services.GetService<IDataService>();
    service.Load();
}

// ✅ Constructor injection — explicit, testable
public class MyViewModel(IDataService dataService)
{
    public void DoWork() => dataService.Load();
}
Use explicit resolution (Handler.MauiContext.Services) only when constructor
injection is genuinely unavailable (e.g., inside a custom handler or
platform callback).

---

Platform-Specific Registration Pitfall

When using #if directives for platform services, ensure the interface is always registered — otherwise consumers on untargeted platforms get a runtime null.

// ❌ No registration on Windows — GetService returns null
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#endif

// ✅ Cover all platforms or provide a no-op fallback
#if ANDROID
builder.Services.AddSingleton<INotificationService, AndroidNotificationService>();
#elif IOS || MACCATALYST
builder.Services.AddSingleton<INotificationService, AppleNotificationService>();
#elif WINDOWS
builder.Services.AddSingleton<INotificationService, WindowsNotificationService>();
#endif

---

Checklist

  • [ ] Every Page and ViewModel that needs injection is registered in MauiProgram.cs
  • [ ] Pages/ViewModels are AddTransient; shared services are AddSingleton
  • [ ] Constructor injection used everywhere possible; service locator only as last resort
  • [ ] Interfaces defined for any service you need to mock in tests
  • [ ] Platform-specific #if registrations cover all target platforms (or provide fallback)
  • [ ] Late-bound services resolved in CreateWindow(), not during XAML parse
  • [ ] AddScoped only used when you manually manage IServiceScope

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.