Runic.Application.ReactiveUI
0.7.0-preview.3
Runic.Application.Views.ReactiveUI
Additional DetailsRenamed to Runic.Application.Views.ReactiveUI in 0.7.0-preview.7. Namespaces are unchanged; replace the package reference.
See the version list below for details.
dotnet add package Runic.Application.ReactiveUI --version 0.7.0-preview.3
NuGet\Install-Package Runic.Application.ReactiveUI -Version 0.7.0-preview.3
<PackageReference Include="Runic.Application.ReactiveUI" Version="0.7.0-preview.3" />
<PackageVersion Include="Runic.Application.ReactiveUI" Version="0.7.0-preview.3" />
<PackageReference Include="Runic.Application.ReactiveUI" />
paket add Runic.Application.ReactiveUI --version 0.7.0-preview.3
#r "nuget: Runic.Application.ReactiveUI, 0.7.0-preview.3"
#:package Runic.Application.ReactiveUI@0.7.0-preview.3
#addin nuget:?package=Runic.Application.ReactiveUI&version=0.7.0-preview.3&prerelease
#tool nuget:?package=Runic.Application.ReactiveUI&version=0.7.0-preview.3&prerelease
ReactiveUI integration for Runic Views
Runic.Application.ReactiveUI is the default ReactiveUI 26 adapter for Runic
Views. It references ReactiveUI's ReactiveUI.Primitives flavor and has no
CommunityToolkit dependency. It provides:
ReactiveRunicView<T>andReactiveRunicWindow<T>, which implementIViewFor<T>over a typed RunicDataContext;ReactiveRunicViewLocator, adapting explicit ReactiveUI view mappings and contracts;ReactiveRoutedRegion<T>, projectingRoutingState.CurrentViewModelinto a generated content property, logging an incompatible route or failed router through an optionalILoggerFactory(Views events 1040-1041);- experimental observables and a back command for
RunicNavigatorregions (see Navigation); - mount-owned
IActivatableViewModelleases; and - typed command, interaction, and model-context scheduler adapters used by the compiled-model generator.
One browser presentation gets one logical Runic View and activation lease. A
shared ViewModel stays activated while any presentation remains mounted.
WindowContentSession.AttachPresentation creates those logical views for
generated content routes. The Reactive Notes
example covers multiple views over one ViewModel, routed content, explicit view
contracts, and activation lifetimes.
New projects can start with ReactiveUI ViewModels:
dotnet new runic-app --view-models reactiveui.
ReactiveUI 26
This package targets ReactiveUI 26.0.1, Binding 9.1.0, Primitives 9.0.0, and SourceGenerators 4.2.0. ReactiveUI 26 and Binding 9 keep the 25/8.x public API; they are majors because their .NET 11 assemblies no longer use runtime-async, so they also work on Mono. Runic's adapter API and generated contracts are unchanged; rebuild the app against the 26 packages.
Primitives 9 changes SubscribeSafe: a single lambda is now the onNext
handler. Rename error-only calls, such as source.SubscribeSafe(ex => Log(ex)),
to SubscribeSafeErrors. Binding 9 also makes BindCommandUnsafe and
BindInteractionUnsafe dispose the binding they replace when the control chain
changes.
When moving from ReactiveUI 24, note that ReactiveUI 25 moved IViewFor<T> and
IViewLocator to ReactiveUI.Binding, so upgrade the Runic adapter, app, and
generated web client together. An adapter binary compiled against ReactiveUI
24 can fail at runtime against ReactiveUI 25 or later even if a dependency
override compiles.
ReactiveUI now includes shared source generators. Remove old explicit generator
pins or update them to 4.2.0 or later. The SDK centrally pins 4.2.0; the same
generator package supports the System.Reactive flavor, so there is no separate
ReactiveUI.Reactive.SourceGenerators package.
Create explicit mappings with the Binding locator:
var locator = new DefaultViewLocator();
locator.CreateMappingBuilder()
.Map<EditorViewModel>(() => new EditorView())
.Map<EditorViewModel>(() => new CompactEditorView(), "compact");
For global lookup, replace ReactiveUI.ViewLocator.Current with
ReactiveUI.Binding.ViewLocator.GetCurrent().
Generated ReactiveUI contracts
Public IReactiveCommand<TInput, TResult> properties are inspected through
their interface contract, so ReactiveCommand, a base type, and a combined
command use the same generated input/result bridge. RxVoid is a no-result
operation and can complete with zero observable values; a non-void result must
produce exactly one observable value unless the property opts into Last or
Stream with [RunicCommandResult(...)].
Generated TypeScript exposes operation start, request-ID recovery, status,
cancellation, retained results, and cursor reads for streams.
Use [RunicCommandInput(typeof(TInput))] on a plain ICommand property to
generate a typed argument. That command remains synchronous fire-and-snapshot
work and has no generated operation result or cancellation handle.
Public getter-only Interaction<TInput, TOutput> properties become typed
browser surfaces. The generated client uses this shape:
const unregister = view.interactions.confirmDiscard.handle(
async (request, { signal }) => confirmInFrontend(request, signal),
);
The disposer, replacement handler, view disposal, unmount, and disconnect abort the handler signal as appropriate. Requests go only to a selected mounted endpoint through a pull route. With no eligible browser handler, the adapter does not consume the interaction, preserving normal ReactiveUI .NET handler precedence and unhandled behavior.
RunicReactiveSchedulerProvider.For(context) returns a context-backed
ISequencer. It serializes scheduled notifications with short Runic model
turns and deliberately does not set ReactiveUI's process-global scheduler.
Use normal host dispatchers for native UI and explicitly marshal background
state changes through the model context.
Call AddRunicReactiveModelContext() before the ViewModel creates its commands
and inject the scoped sequencer. Bind the root and every independently
presented child to that same context:
services.AddRunicReactiveModelContext();
public EditorViewModel(
EditorSession session,
IRunicModelContext modelContext,
ISequencer scheduler)
{
_scheduler = scheduler;
Workspace = new EditorWorkspaceViewModel(session, this, _scheduler);
_contextLease = RunicModelContextRegistry.Shared.Bind(modelContext, this, Workspace);
}
protected ReactiveCommand<string, RxVoid> CreateCommand(Func<string, Task> work) =>
ReactiveCommand.CreateFromTask<string>(work, _scheduler);
Execute may complete before its scheduled IsExecuting and CanExecute
notifications arrive. The context-backed scheduler orders those notifications
with bridge replies and state publication. A default headless scheduler may
deliver them later; do not solve that by changing ReactiveUI's global scheduler.
Bind dynamically created or independently presented children to the same
context and retain their leases until their presentation is removed.
The extension uses TryAdd for the scoped IRunicModelContext, singleton
IRunicReactiveSchedulerProvider, and scoped ISequencer, preserving custom
application registrations. Dispose a scope asynchronously to drain its default
owned context. Each queued context/scheduler item captures its own
ExecutionContext, so a trusted interaction scope follows its own deferred
work without leaking to another queued operation.
The ReactiveUI reference guide defines the supported data shapes, operation semantics, interaction targeting, and model-context ownership.
Declared failures and ThrownExceptions
A ReactiveCommand reports every exception on ThrownExceptions, including a
declared RunicFailureException ([RunicFailure]) that the Bridge already sent
to the client as domain-failed. Without a subscriber, ReactiveUI routes it to
RxState.DefaultExceptionHandler, which breaks into the debugger and throws
UnhandledErrorException. Every bridged ReactiveCommand therefore needs a
ThrownExceptions subscriber: this helper, or the application's own.
_saveExceptions = SaveCommand.ObserveBridgeExceptions(logger);
_discardExceptions = DiscardCommand.ObserveBridgeExceptions(error => status.Report(error));
ObserveBridgeExceptions ignores RunicFailureException and
OperationCanceledException (ReactiveUI also reports a cancelled operation on
ThrownExceptions; the client already received both) and reports every other
exception: the ILogger overload logs it at Error as ReactiveCommandFailed
(event 1042) with the command's expression, such as SaveCommand, as Source
(pass sourceName to choose another), and the callback overload passes it on.
Both overloads, including the logger, also ignore an OperationCanceledException
that is not the Bridge's, such as an HttpClient timeout when the command runs
outside the Bridge; subscribe to ThrownExceptions yourself to report those. While it is attached, no
exception of that command reaches
RxState.DefaultExceptionHandler. An exception thrown by the callback surfaces
from ThrownExceptions.OnNext, inside ReactiveUI. Dispose the returned
subscription with the ViewModel. An unexpected exception from a Bridge call is
also logged by the Views runtime (event 1000 or 1004). The helper works on any
IHandleObservableErrors, such as a ReactiveObject.
Navigation (experimental)
RunicNavigator regions replace RoutingState rather than wrap it: a region
has awaited departure guards, stable entry ids, owned content and supersession,
which RoutingState's mutable stack and synchronous Navigate cannot enforce.
Use a NavigationRegion<TContent> for new navigation, and keep
ReactiveRoutedRegion<T> for existing RoutingState code. Expose the
region as a get-only property and the generator presents its Current like
any content slot.
The adapter is experimental, like the navigator: suppress RUNICNAV001 to use it.
var scheduler = new RunicReactiveSchedulerProvider().For(context);
BackCommand = Main.CreateBackCommand(scheduler).DisposeWith(disposables); // holds a region handler
BackCommand.ObserveBridgeExceptions(logger).DisposeWith(disposables);
Main.WhenCurrentChanged() // TContent?, distinct by instance
.Select(current => current is DocumentViewModel)
.ObserveOn(scheduler)
.Subscribe(isDocument => IsDocumentOpen = isDocument)
.DisposeWith(disposables);
WhenCurrentChanged()emits the current content (nullwhen the region is empty) on subscription and then each different instance.WhenEntryChanged()emits eachNavigationEntry<TContent>, also when two entries present the same borrowed instance.- Both emit the initial value on the subscribing thread, inside
Subscribe, and later values on the model turn that raises the change: the commit turn for a navigation. UseObserveOnto deliver elsewhere. They never complete, and stop when the subscription is disposed. CreateBackCommand(scheduler)returns aReactiveCommand<RxVoid, NavigationResult<TContent>>(ReactiveCommand<Unit, NavigationResult<TContent>>in the System.Reactive flavor). It can execute whileCanGoBackis true andIsTransitioningis false, including transitions it did not start. It reflects this region only: a transition of an ancestor region does not disable it. The command observes the region until it is disposed, so dispose it with its owner. A rejected, superseded or failed Back is its output, not an exception, soThrownExceptionscarries only defects and cancellation.- Activation is not entry lifetime. ReactiveUI activation follows a mounted
View. A navigation entry lives from its push until it retires: a retained
entry stays alive and keeps its state while nothing presents it, and its View
deactivates and activates again when the entry returns. Put per-presentation
subscriptions in
WhenActivated. Use the navigator's hooks (INavigationInitialize,INavigationResume,INavigationDepartureGuard) and the entry'sRetirementtoken for per-entry work, andDisposefor owned content.
System.Reactive flavor
For DynamicData changesets, place BatchBridgeSnapshots(model) after
ObserveOn(modelSequencer) and before Bind or SortAndBind. The downstream
delivery owns the batch even when scheduling is deferred. Annotate a read-only
DTO collection with [RunicCollection(nameof(Row.Id))] to publish indexed
updates. See the DynamicData guide.
Applications using ReactiveUI.Reactive, ReactiveUI.Binding.Reactive,
System.Reactive.Unit, or IScheduler should instead reference
Runic.Application.ReactiveUI.Reactive.
The two packages expose distinct ReactiveUI namespaces and must not be mixed in
one application. If a generic interface command does not reveal its flavor to
the compiled-model generator, set
RunicBridgeReactiveUiFlavor=reactive in the project that generates the
bridge.
See the upstream Binding migration guide for ReactiveUI's application-level migration details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.12)
- ReactiveUI (>= 26.0.1)
- ReactiveUI.Binding (>= 9.1.0)
- ReactiveUI.SourceGenerators (>= 4.2.0)
- Runic.Application (= 0.7.0-preview.3)
- Splat (>= 21.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 0.7.0-preview.6 | 1,106 | 10/9/2026 | |
| 0.7.0-preview.5 | 154 | 10/9/2026 | |
| 0.7.0-preview.4 | 92 | 10/8/2026 | |
| 0.7.0-preview.3 | 231 | 10/8/2026 | |
| 0.7.0-preview.2 | 92 | 10/7/2026 | |
| 0.7.0-preview.1 | 584 | 10/7/2026 | |
| 0.6.0-preview.1 | 361 | 10/5/2026 | |
| 0.5.0-preview.3 | 76 | 10/1/2026 | |
| 0.5.0-preview.2 | 79 | 9/25/2026 |