DEV Community

Cover image for Stop fighting loading dialogs in .NET MAUI: a native loader in one line
Pratham Gheewala
Pratham Gheewala

Posted on

Stop fighting loading dialogs in .NET MAUI: a native loader in one line

Plugin.Maui.LoadingHud is a tiny native loading overlay for .NET MAUI. Lottie, image or spinner, configured once and overridable per screen.

Every app needs a loading indicator. It sounds like the simplest part of a project.

Then you build one in .NET MAUI, and the problems start.

The problem

Most MAUI loaders are built on pages, popups or modal navigation. That works for a demo, but in a real app it causes trouble:

  • 😵 Showing the loader fires OnAppearing / OnDisappearing on your pages
  • 🔒 Calling Show() inside another Show() can leave the loader stuck on screen
  • 🪟 Two loaders can overlap
  • 🔁 You repeat the same colors, text and animation on every screen

I hit all of these in a production app, so I stopped patching and built a loader that doesn't go through MAUI navigation at all.

The solution: Plugin.Maui.LoadingHud ✨

It's a native overlay (an Android Dialog and an iOS UIWindow). Your pages never know it's there, so no lifecycle events fire.

using (LoadingHud.Show("Loading..."))
    await api.GetDataAsync();
Enter fullscreen mode Exit fullscreen mode

When the using block ends, the loader closes, even if your code throws.

Android Demo
iOS Demo

Set it up once

Add your defaults in MauiProgram.cs. Every option is optional.

builder.UseLoadingHud(o =>
{
    o.Text            = "Please wait...";               // default text everywhere
    o.Lottie          = "loader.json";                  // Resources/Raw
    o.Image           = "logo.png";                     // Resources/Images
    o.IndicatorColor  = Colors.White;
    o.BackgroundColor = Color.FromArgb("#80000000");
    o.OverlayColor    = Color.FromArgb("#66000000");
    o.TextColor       = Colors.White;
    o.IndicatorSize   = 40;
    o.LottieSize      = 100;
    o.ImageSize       = 60;
    o.Spacing         = 8;                              // gap between loader and text
});
Enter fullscreen mode Exit fullscreen mode

Set nothing and you get a clean white spinner.

Change it for one screen

using (LoadingHud.Show("Uploading...", lottie: "upload.json")) { ... }
using (LoadingHud.Show(image: "logo.png")) { ... }
using (LoadingHud.Show("")) { ... }        // no text this time
Enter fullscreen mode Exit fullscreen mode

The signature is simple:

IDisposable Show(string? text = null, string? lottie = null, string? image = null)
Enter fullscreen mode Exit fullscreen mode

How it decides what to show

Priority Loader
1 lottie passed to Show()
2 image passed to Show()
3 Lottie from MauiProgram
4 Image from MauiProgram
5 Activity indicator (spinner)

Text works the same way: the text you pass wins, otherwise the default from MauiProgram. If a Lottie or image file can't be found, it quietly falls back to the spinner instead of crashing.

How it works under the hood

  • Native overlay: Android uses a Dialog and iOS uses a UIWindow above your app. MAUI navigation is never involved.
  • Reference counting: each Show() adds one, each Dispose() removes one, and the loader closes when the count reaches zero. Nested calls just work.
  • Hide() as a safety net: it closes everything at once, and tokens from before it are ignored.
  • Memory: native views are disposed on hide. On iOS only the last Lottie stays cached, and Android uses Lottie's own cache.
  • Threading: call Show() / Hide() from any thread. The UI work is marshalled to the main thread.

Quick example

private async void Save_Clicked(object sender, EventArgs e)
{
    using (LoadingHud.Show("Saving..."))
    {
        await SaveAsync();

        using (LoadingHud.Show("Syncing..."))   // nested: no clash
            await SyncAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

Get it

📦 NuGet: https://www.nuget.org/packages/Plugin.Maui.LoadingHud
💻 GitHub: https://github.com/PrathamsGithub/Plugin.Maui.LoadingHud

dotnet add package Plugin.Maui.LoadingHud
Enter fullscreen mode Exit fullscreen mode

Android and iOS are supported. Other targets compile but show nothing.

Feedback welcome

Found a bug on a device I didn't test, or want another option? Open an issue or send a PR on GitHub. If it saves you time, a ⭐ helps others find it. 🙌

Top comments (0)