4. Application startup: AppBuilder and lifetimes

Goal

Why this matters

Prerequisites

1. Follow the AppBuilder pipeline step by step

Program.cs (or Program.fs in F#) is the entry point. A typical template looks like this:

using Avalonia;
using Avalonia.ReactiveUI; // optional in ReactiveUI template

internal static class Program
{
    [STAThread]
    public static void Main(string[] args) => BuildAvaloniaApp()
        .StartWithClassicDesktopLifetime(args);

    public static AppBuilder BuildAvaloniaApp()
        => AppBuilder.Configure<App>()       // 1. Choose your Application subclass
            .UsePlatformDetect()             // 2. Detect the right native backend (Win32, macOS, X11, Android, iOS, Browser)
            .UseSkia()                      // 3. Configure the rendering pipeline (Skia GPU/CPU renderer)
            .With(new SkiaOptions {         // 4. (Optional) tweak renderer settings
                MaxGpuResourceSizeBytes = 96 * 1024 * 1024
            })
            .LogToTrace()                   // 5. Hook logging before startup completes
            .UseReactiveUI();               // 6. (Optional) enable ReactiveUI integration
}

Each call returns the builder so you can chain configuration. Relevant source:

Builder pipeline diagram (mental map)

Program.Main
  `--- BuildAvaloniaApp()
        |-- Configure<App>()        (create Application instance)
        |-- UsePlatformDetect()     (choose backend)
        |-- UseSkia()/UseReactiveUI (features)
        |-- LogToTrace()/With(...)  (diagnostics/options)
        `-- StartWith...Lifetime()  (select lifetime and enter main loop)

If anything in the pipeline throws, the process exits before UI renders. Log early to catch those cases.

2. Lifetimes in detail

Lifetime type Purpose Typical targets Key members
ClassicDesktopStyleApplicationLifetime Windowed desktop apps with startup/shutdown events and main window Windows, macOS, Linux MainWindow, ShutdownMode, Exit, ShutdownRequested, OnExit
SingleViewApplicationLifetime Hosts a single root control (MainView) Android, iOS, Embedded MainView, MainViewClosing, OnMainViewClosed
BrowserSingleViewLifetime (implements ISingleViewApplicationLifetime) Same contract as single view, tuned for WebAssembly Browser (WASM) MainView, async app init
HeadlessApplicationLifetime No visible UI; runs for tests or background services Unit/UI tests TryGetTopLevel(), manual pumping

Key interfaces and classes to read:

Desktop lifetime flow

Single view and browser lifetimes

Headless lifetime notes

| Purpose | Typical targets | Key members | | --- | --- | --- | --- | | ClassicDesktopStyleApplicationLifetime | Windowed desktop apps with startup/shutdown events and main window | Windows, macOS, Linux | MainWindow, ShutdownMode, Exit, OnExit | | SingleViewApplicationLifetime | Hosts a single root control (MainView) | Android, iOS, Embedded | MainView, MainViewClosing, OnMainViewClosed | | BrowserSingleViewLifetime (implements ISingleViewApplicationLifetime) | Same contract as single view, tuned for WebAssembly | Browser (WASM) | MainView, async app init | | HeadlessApplicationLifetime | No visible UI; runs for tests or background services | Unit/UI tests | TryGetTopLevel(), manual pumping |

Key interfaces and classes to read:

3. Wiring lifetimes in App.OnFrameworkInitializationCompleted

App.axaml.cs is the right place to react once the framework is ready:

using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Microsoft.Extensions.DependencyInjection; // if using DI

namespace MultiLifetimeSample;

public partial class App : Application
{
    private IServiceProvider? _services;

    public override void Initialize()
        => AvaloniaXamlLoader.Load(this);

    public override void OnFrameworkInitializationCompleted()
    {
        // Create/register services only once
        _services ??= ConfigureServices();

        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            var shell = _services.GetRequiredService<MainWindow>();
            desktop.MainWindow = shell;
            desktop.Exit += (_, _) => _services.Dispose();
        }
        else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
        {
            singleView.MainView = _services.GetRequiredService<MainView>();
        }
        else if (ApplicationLifetime is IControlledApplicationLifetime controlled)
        {
            controlled.Exit += (_, _) => Console.WriteLine("Application exited");
        }

        base.OnFrameworkInitializationCompleted();
    }

    private IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();
        services.AddSingleton<MainWindow>();
        services.AddSingleton<MainView>();
        services.AddSingleton<DashboardViewModel>();
        services.AddLogging(builder => builder.AddDebug());
        return services.BuildServiceProvider();
    }
}

Notes:

4. Handling exceptions and logging

Important logging points:

Example:

[STAThread]
public static void Main(string[] args)
{
    AppDomain.CurrentDomain.UnhandledException += (_, e) => LogFatal(e.ExceptionObject);
    TaskScheduler.UnobservedTaskException += (_, e) => LogFatal(e.Exception);

    Dispatcher.UIThread.UnhandledException += (_, e) =>
    {
        LogFatal(e.Exception);
        e.Handled = true; // optionally keep the app alive after logging
    };

    try
    {
        BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
    }
    catch (Exception ex)
    {
        LogFatal(ex);
        throw;
    }
}

ClassicDesktopStyleApplicationLifetime exposes ShutdownMode, ShutdownRequested, and Shutdown() so you can decide whether to exit on last window close, on main window close, or only when you call Shutdown() explicitly.

5. Switching lifetimes inside one project

You can provide different entry points or compile-time switches:

public static void Main(string[] args)
{
#if HEADLESS
    BuildAvaloniaApp().Start(AppMain);
#elif BROWSER
    BuildAvaloniaApp().SetupBrowserApp("app");
#else
    BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
#endif
}

6. Headless/testing scenarios

Avalonia's headless assemblies let you boot an app without rendering:

using Avalonia;
using Avalonia.Headless;

public static class Program
{
    public static void Main(string[] args)
        => BuildAvaloniaApp().StartWithHeadless(new HeadlessApplicationOptions
        {
            RenderingMode = HeadlessRenderingMode.None,
            UseHeadlessDrawingContext = true
        });
}

7. Putting it together: desktop + single-view sample

Program.cs:

public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>()
    .UsePlatformDetect()
    .UseSkia()
    .LogToTrace();

[STAThread]
public static void Main(string[] args)
{
    if (args.Contains("--single-view"))
    {
        BuildAvaloniaApp().StartWithSingleViewLifetime(new MainView());
    }
    else
    {
        BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
    }
}

App.axaml.cs sets up both MainWindow and MainView (as shown earlier). At runtime, you can switch lifetimes via command-line or compile condition.

Troubleshooting

Practice and validation

  1. Modify your project so the same App supports both desktop and single-view lifetimes. Use a command-line switch (--mobile) to select StartWithSingleViewLifetime and verify your MainView renders inside a mobile head (Android emulator or dotnet run -- --mobile + SingleView desktop simulation).
  2. Register a logging provider using Microsoft.Extensions.Logging. Log the current lifetime type inside OnFrameworkInitializationCompleted, subscribe to ShutdownRequested, and record when the app exits.
  3. Add a simple DI container (as shown) and resolve MainWindow/MainView through it. Confirm disposal happens when the app exits.
  4. Create a headless console entry point (BuildAvaloniaApp().Start(AppMain)) and run a unit test that constructs a view, invokes bindings, and pumps the dispatcher.
  5. Wire Dispatcher.UIThread.UnhandledException and verify that handled exceptions keep the app alive while unhandled ones terminate.
  6. Intentionally throw inside OnFrameworkInitializationCompleted and observe how logging captures the stack. Then add a try/catch to show a fallback dialog or log and exit gracefully.

Look under the hood (source bookmarks)

Check yourself

What's next