Skip to content

Docs: improve RxAppBuilder, custom DI integration, and builder usage documentation #938

@glennawatson

Description

@glennawatson

Context

Feedback from reactiveui/ReactiveUI#4296 by @ChaseFlorell highlights several documentation gaps that create confusion around the v21+ builder pattern, custom DI container integration, and how to configure global settings. The builder pattern is staying — it's required for AOT — but we need to better document the flexibility that already exists.

Problems to Address

1. Builder return value confusion

Users think they must capture the BuildApp() return into a variable, then IDEs warn about unused variables. We need to show:

  • You don't have to assign the return value — RxAppBuilder.CreateReactiveUIBuilder().WithBlazor().BuildApp(); is valid
  • When you should capture it — to access MainThreadScheduler, TaskpoolScheduler, or WithInstance<T>()

2. Custom DI container + builder integration is undocumented

The existing custom-dependency-inversion.md page shows the old InitializeSplat()/InitializeReactiveUI() pattern. It doesn't show how to use RxAppBuilder with a custom container like Autofac, DryIoc, or Microsoft.Extensions.DependencyInjection. Key APIs that exist but aren't documented well:

  • resolver.CreateReactiveUIBuilder() — extension method on IMutableDependencyResolver that creates a builder using your resolver
  • builder.WithRegistration(resolver => { ... }) — register into your own resolver
  • builder.UsingSplatModule<T>() — use existing Splat adapter modules
  • builder.ForCustomPlatform(scheduler, resolver => { ... }) — full custom setup

3. DefaultExceptionHandler docs are outdated

default-exception-handler.md references RxApp.DefaultExceptionHandler which no longer exists in v21+. It needs to show the builder path (WithExceptionHandler()) and the direct API (once reactiveui/ReactiveUI#4302 lands).

4. No "existing app / hybrid bootstrap" migration scenario

The migration guide covers fresh setups but doesn't address the most common real-world case: "I already have a working app with its own DI and bootstrap — how do I integrate RxAppBuilder?"

Specific File Changes

Update: docs/handbook/rxappbuilder.md

Add a section "Using the BuildApp() Return Value" after the existing "Example Usage" section:

## Using the BuildApp() Return Value

`BuildApp()` returns an `IReactiveUIInstance`. You can use it to access configured schedulers
and resolve registered services:

\`\`\`csharp
var app = RxAppBuilder.CreateReactiveUIBuilder()
    .WithWpf()
    .BuildApp();

// Access schedulers
var mainScheduler = app.MainThreadScheduler;
var taskPoolScheduler = app.TaskpoolScheduler;

// Resolve registered services post-build
app.WithInstance<IScreen>(screen =>
    screen.Router.Navigate.Execute(new MainViewModel(screen)));
\`\`\`

If you don't need the return value, you don't have to assign it:

\`\`\`csharp
// Perfectly valid — no variable needed, no IDE warnings
RxAppBuilder.CreateReactiveUIBuilder()
    .WithBlazor()
    .RegisterViews(v => v.Map<MyViewModel, MyView>())
    .BuildApp();
\`\`\`

Also add a section "Using RxAppBuilder with a Custom DI Container":

## Using RxAppBuilder with a Custom DI Container

If you use a custom Splat resolver (Autofac, DryIoc, Microsoft DI, etc.),
you can create the builder from your resolver directly:

\`\`\`csharp
// Set up your custom Splat adapter first
var autofacResolver = containerBuilder.UseAutofacDependencyResolver();

// Create the ReactiveUI builder using YOUR resolver
var app = autofacResolver
    .CreateReactiveUIBuilder()   // extension method on IMutableDependencyResolver
    .WithWpf()
    .WithRegistration(resolver =>
    {
        resolver.RegisterLazySingleton<IScreen>(() => new MainViewModel());
    })
    .BuildApp();
\`\`\`

The `CreateReactiveUIBuilder()` extension method on `IMutableDependencyResolver`
(in `ReactiveUI.Builder.RxAppBuilder`) wires the builder to use your container
instead of the default Splat locator. All registrations made through
`WithRegistration()` flow into your container.

Update: docs/handbook/dependency-inversion/custom-dependency-inversion.md

The current examples use the legacy InitializeSplat() / InitializeReactiveUI() calls. Add a prominent note at the top:

> **ReactiveUI v21.0.1+**: If you are using the RxAppBuilder (recommended),
> see [Using RxAppBuilder with a Custom DI Container](~/docs/handbook/rxappbuilder.md#using-rxappbuilder-with-a-custom-di-container)
> for the modern approach. The patterns below still work for users not using the builder.

Also update the Autofac example to show the builder-integrated version alongside the legacy version.

Update: docs/handbook/default-exception-handler.md

The page references RxApp.DefaultExceptionHandler which no longer exists. Update to show:

## Configuring the Exception Handler

### Via RxAppBuilder (recommended)

\`\`\`csharp
RxAppBuilder.CreateReactiveUIBuilder()
    .WithWpf()
    .WithExceptionHandler(new MyCoolObservableExceptionHandler())
    .BuildApp();
\`\`\`

### Direct configuration (without builder)

\`\`\`csharp
// Available for apps with existing bootstrap pipelines
RxState.SetDefaultExceptionHandler(new MyCoolObservableExceptionHandler());
\`\`\`

> **Note**: The direct method is available from ReactiveUI vX.X+ (see reactiveui/ReactiveUI#4302).

Update: docs/upgrading/rxappbuilder-migration.md

Add a new section "Scenario: Existing App with Custom DI" under Common Migration Scenarios:

### Scenario: Existing App with Custom DI Container

If you already have a fully wired DI container (Autofac, Microsoft DI, etc.)
and an existing bootstrap pipeline:

\`\`\`csharp
// 1. Set up your container as usual
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<MyService>().As<IMyService>();

// 2. Create the Splat adapter
var autofacResolver = containerBuilder.UseAutofacDependencyResolver();
containerBuilder.RegisterInstance(autofacResolver);

// 3. Create the ReactiveUI builder FROM your resolver
autofacResolver
    .CreateReactiveUIBuilder()
    .WithBlazor()
    .RegisterViews(v => v
        .Map<LoginViewModel, LoginView>()
        .Map<MainViewModel, MainView>()
    )
    .BuildApp();

// 4. Continue your normal bootstrap
var container = containerBuilder.Build();
autofacResolver.SetLifetimeScope(container);
\`\`\`

**Key point**: `resolver.CreateReactiveUIBuilder()` is an extension method on
`IMutableDependencyResolver`. It creates a builder that uses YOUR container —
all ReactiveUI service registrations flow into it. You don't need to call
`InitializeSplat()` or `InitializeReactiveUI()` when using the builder.

TOC changes needed

No new TOC entries required — all changes are to existing pages. If we decide to add a dedicated docs/handbook/dependency-inversion/builder-integration.md page later, add to docs/handbook/dependency-inversion/toc.yml:

- name: Builder Integration with Custom DI
  href: builder-integration.md

Summary of files to edit

File Change
docs/handbook/rxappbuilder.md Add "Using the BuildApp() Return Value" and "Using RxAppBuilder with a Custom DI Container" sections
docs/handbook/dependency-inversion/custom-dependency-inversion.md Add v21+ note at top, add builder-integrated Autofac example
docs/handbook/default-exception-handler.md Update to show builder and direct API paths (remove stale RxApp.DefaultExceptionHandler reference)
docs/upgrading/rxappbuilder-migration.md Add "Existing App with Custom DI Container" migration scenario

Related: reactiveui/ReactiveUI#4296, reactiveui/ReactiveUI#4302, reactiveui/ReactiveUI#4303

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions