diff --git a/src/ReactiveUI/Activation/IActivationForViewFetcher.cs b/src/ReactiveUI/Activation/IActivationForViewFetcher.cs index f6bb9d0b53..0a5d181561 100644 --- a/src/ReactiveUI/Activation/IActivationForViewFetcher.cs +++ b/src/ReactiveUI/Activation/IActivationForViewFetcher.cs @@ -10,6 +10,31 @@ namespace ReactiveUI; /// View is activated or deactivated. This is usually only used when porting /// ReactiveUI to a new UI framework. /// +/// +/// +/// Activation fetchers translate framework-specific signals (such as page navigation, focus, or visibility +/// changes) into the cross-platform semantics used by ReactiveUI. Multiple +/// fetchers can exist, each advertising an affinity for a given view type. +/// +/// +/// +/// +/// typeof(Form).IsAssignableFrom(view) ? 10 : 0; +/// +/// public IObservable GetActivationForView(IActivatableView view) +/// { +/// var form = (Form)view; +/// return Observable.Merge( +/// Observable.FromEventPattern(form, nameof(form.Load)).Select(_ => true), +/// Observable.FromEventPattern(form, nameof(form.FormClosed)).Select(_ => false)); +/// } +/// } +/// ]]> +/// +/// public interface IActivationForViewFetcher { /// diff --git a/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs b/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs index 22993c0ab8..6443b78375 100644 --- a/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs +++ b/src/ReactiveUI/Bindings/Interaction/InteractionBindingMixins.cs @@ -8,6 +8,24 @@ namespace ReactiveUI; /// /// This class provides extension methods for the ReactiveUI view binding mechanism. /// +/// +/// +/// Interaction bindings are usually established within a view's activation block to ensure registrations are disposed +/// when the view is no longer visible. The helpers resolve the on the view +/// model via an expression and hook it to a handler that can await UI prompts such as dialogs. +/// +/// +/// +/// +/// +/// { +/// this.BindInteraction(ViewModel, vm => vm.ShowDialog, HandleDialogAsync) +/// .DisposeWith(disposables); +/// }); +/// ]]> +/// +/// public static class InteractionBindingMixins { private static readonly InteractionBinderImplementation _binderImplementation = new(); diff --git a/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs b/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs index 28bf1aea49..b12207ec70 100644 --- a/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs +++ b/src/ReactiveUI/Bindings/Property/PropertyBindingMixins.cs @@ -8,6 +8,28 @@ namespace ReactiveUI; /// /// This class provides extension methods for the ReactiveUI view binding mechanism. /// +/// +/// +/// The helpers in this class are typically consumed within a view's WhenActivated block to connect a view model +/// property to a control and automatically dispose the binding when the view deactivates. Converters can be supplied via +/// instances or delegates, and indicates whether bindings +/// push values from the view model, the view, or both. +/// +/// +/// +/// +/// +/// { +/// this.Bind(ViewModel, vm => vm.UserName, v => v.UserNameTextBox.Text) +/// .DisposeWith(disposables); +/// +/// this.OneWayBind(ViewModel, vm => vm.IsBusy, v => v.Spinner.IsRunning) +/// .DisposeWith(disposables); +/// }); +/// ]]> +/// +/// public static class PropertyBindingMixins { private static readonly PropertyBinderImplementation _binderImplementation; diff --git a/src/ReactiveUI/Bindings/Reactive/IReactiveBinding.cs b/src/ReactiveUI/Bindings/Reactive/IReactiveBinding.cs index 03de92f872..331a3c51d8 100644 --- a/src/ReactiveUI/Bindings/Reactive/IReactiveBinding.cs +++ b/src/ReactiveUI/Bindings/Reactive/IReactiveBinding.cs @@ -13,6 +13,24 @@ namespace ReactiveUI; /// /// The view type. /// The value type. +/// +/// +/// instances are returned from helpers like Bind, +/// OneWayBind, and BindCommand. Holding onto the binding allows you to inspect the original +/// property expressions, observe the stream, or dispose the binding manually when a +/// different lifecycle than WhenActivated is required. +/// +/// +/// +/// +/// vm.UserName, v => v.UserNameTextBox.Text); +/// binding.Changed.Subscribe(value => logger.LogInformation("UserName changed to {Value}", value)); +/// // Later +/// binding.Dispose(); +/// ]]> +/// +/// public interface IReactiveBinding : IDisposable where TView : IViewFor { diff --git a/src/ReactiveUI/Builder/IReactiveUIBuilder.cs b/src/ReactiveUI/Builder/IReactiveUIBuilder.cs index 74b15a33b2..0ac177fcdc 100644 --- a/src/ReactiveUI/Builder/IReactiveUIBuilder.cs +++ b/src/ReactiveUI/Builder/IReactiveUIBuilder.cs @@ -9,8 +9,31 @@ namespace ReactiveUI.Builder; /// -/// IReactiveUIBuilder. +/// Fluent builder that configures ReactiveUI platform services, registrations, and schedulers before building an application instance. /// +/// +/// +/// The builder wraps primitives so apps can register views, view models, and platform modules using +/// a single fluent API. Most hosts call UseReactiveUI (MAUI) or services.AddReactiveUI() (generic host) internally, which +/// creates an and then applies platform-specific extensions. +/// +/// +/// +/// +/// +/// config +/// .WithPlatformServices() +/// .RegisterView() +/// .RegisterSingletonViewModel() +/// .WithRegistration(resolver => +/// { +/// resolver.RegisterLazySingleton(() => new ApiClient(), typeof(IApiClient)); +/// }) +/// .BuildApp()); +/// ]]> +/// +/// /// public interface IReactiveUIBuilder : IAppBuilder { diff --git a/src/ReactiveUI/Interactions/IInteraction.cs b/src/ReactiveUI/Interactions/IInteraction.cs index 7084bbf862..8d9dd650cc 100644 --- a/src/ReactiveUI/Interactions/IInteraction.cs +++ b/src/ReactiveUI/Interactions/IInteraction.cs @@ -21,6 +21,31 @@ namespace ReactiveUI; /// then provides the interaction with an output as the answer to the question. /// /// +/// +/// +/// ConfirmExport { get; } = new(); +/// +/// public Task TryExportAsync(ExportRequest request) => ConfirmExport.Handle(request).ToTask(); +/// } +/// +/// public partial class ExportView : ReactiveUserControl +/// { +/// public ExportView() +/// { +/// this.WhenActivated(disposables => +/// ViewModel!.ConfirmExport.RegisterHandler(async context => +/// { +/// var decision = await dialogService.ShowAsync(context.Input); +/// context.SetOutput(decision); +/// }).DisposeWith(disposables)); +/// } +/// } +/// ]]> +/// +/// /// /// The interaction's input type. /// diff --git a/src/ReactiveUI/Interactions/Interaction.cs b/src/ReactiveUI/Interactions/Interaction.cs index 1f23d2cf8d..40afa3c7df 100644 --- a/src/ReactiveUI/Interactions/Interaction.cs +++ b/src/ReactiveUI/Interactions/Interaction.cs @@ -33,15 +33,41 @@ namespace ReactiveUI; /// if no handler handles the interaction. /// /// +/// +/// +/// ConfirmDelete { get; } = new(); +/// +/// public async Task TryDeleteAsync(string customerName) +/// { +/// var approved = await ConfirmDelete.Handle($"Delete {customerName}?"); +/// return approved; +/// } +/// } +/// +/// public partial class DeleteCustomerView : ReactiveUserControl +/// { +/// public DeleteCustomerView() +/// { +/// this.WhenActivated(disposables => +/// ViewModel!.ConfirmDelete.RegisterHandler(async context => +/// { +/// var approved = await dialogService.ShowAsync(context.Input); +/// context.SetOutput(approved); +/// }).DisposeWith(disposables)); +/// } +/// } +/// ]]> +/// +/// /// /// The interaction's input type. /// /// /// The interaction's output type. /// -/// -/// Initializes a new instance of the class. -/// /// /// The scheduler to use when invoking handlers, which defaults to CurrentThreadScheduler.Instance if . /// @@ -117,7 +143,7 @@ protected Func, IObservable>[] GetHan } /// - /// Gets a interaction context which is used to provide information about the interaction. + /// Gets an interaction context which is used to provide information about the interaction. /// /// The input that is being passed in. /// The interaction context. diff --git a/src/ReactiveUI/Interactions/InteractionContext.cs b/src/ReactiveUI/Interactions/InteractionContext.cs index 6e36f62917..e809874422 100644 --- a/src/ReactiveUI/Interactions/InteractionContext.cs +++ b/src/ReactiveUI/Interactions/InteractionContext.cs @@ -14,7 +14,23 @@ namespace ReactiveUI; /// the input to the interaction, whilst the method allows a handler to provide the /// output. /// +/// +/// Calling more than once throws an , ensuring the +/// handler's reply remains deterministic even when multiple handlers run concurrently. Use +/// to guard logic that should only execute once. +/// /// +/// +/// +/// +/// { +/// var approved = await dialogService.ShowAsync(ctx.Input); +/// ctx.SetOutput(approved); +/// }); +/// ]]> +/// +/// /// /// The type of the interaction's input. /// diff --git a/src/ReactiveUI/Interactions/UnhandledInteractionException.cs b/src/ReactiveUI/Interactions/UnhandledInteractionException.cs index 7611ac98ee..8d82acfcdc 100644 --- a/src/ReactiveUI/Interactions/UnhandledInteractionException.cs +++ b/src/ReactiveUI/Interactions/UnhandledInteractionException.cs @@ -25,10 +25,10 @@ public class UnhandledInteractionException : Exception /// /// Initializes a new instance of the class. /// - /// The interaction that doesn't have a input handler. + /// The interaction that doesn't have an input handler. /// The input into the interaction. public UnhandledInteractionException(Interaction interaction, TInput input) - : this("Failed to find a registration for a Interaction.") + : this("Failed to find a registration for an Interaction.") { _interaction = interaction; Input = input; diff --git a/src/ReactiveUI/Interfaces/IActivatableViewModel.cs b/src/ReactiveUI/Interfaces/IActivatableViewModel.cs index fe6e06ecda..af8e6f627b 100644 --- a/src/ReactiveUI/Interfaces/IActivatableViewModel.cs +++ b/src/ReactiveUI/Interfaces/IActivatableViewModel.cs @@ -12,10 +12,36 @@ namespace ReactiveUI; /// the View is activated. See the documentation for ViewModelActivator to /// read more about Activation. /// +/// +/// +/// Typical usage involves creating a field and calling WhenActivated +/// in the constructor to compose subscriptions that should live only while the view is displayed. +/// +/// +/// +/// +/// +/// { +/// LoadCommand.Execute().Subscribe().DisposeWith(disposables); +/// }); +/// } +/// +/// public ViewModelActivator Activator { get; } +/// } +/// ]]> +/// +/// public interface IActivatableViewModel { /// /// Gets the Activator which will be used by the View when Activation/Deactivation occurs. /// ViewModelActivator Activator { get; } -} \ No newline at end of file +} diff --git a/src/ReactiveUI/Interfaces/IRoutableViewModel.cs b/src/ReactiveUI/Interfaces/IRoutableViewModel.cs index 4815bc1451..5e5894ed75 100644 --- a/src/ReactiveUI/Interfaces/IRoutableViewModel.cs +++ b/src/ReactiveUI/Interfaces/IRoutableViewModel.cs @@ -6,21 +6,40 @@ namespace ReactiveUI; /// -/// Implement this interface for ViewModels that can be navigated to. +/// Defines the minimum contract for view models that participate in navigation. /// +/// +/// +/// Routable view models expose a user-readable used for diagnostics / navigation breadcrumbs +/// and keep a reference to the owning so that downstream navigation commands can be issued. +/// +/// +/// +/// +/// HostScreen = hostScreen; +/// +/// public string? UrlPathSegment => "settings"; +/// +/// public IScreen HostScreen { get; } +/// } +/// ]]> +/// +/// public interface IRoutableViewModel : IReactiveObject { /// - /// Gets a string token representing the current ViewModel, such as 'login' or 'user'. + /// Gets a string token representing the current view model, such as "login" or "user". /// #pragma warning disable CA1056 // URI-like properties should not be strings string? UrlPathSegment { get; } #pragma warning restore CA1056 // URI-like properties should not be strings /// - /// Gets the IScreen that this ViewModel is currently being shown in. This - /// is usually passed into the ViewModel in the Constructor and saved - /// as a ReadOnly Property. + /// Gets the instance that hosts this view model. Use this reference to access the + /// shared when chaining navigation from child view models. /// IScreen HostScreen { get; } -} \ No newline at end of file +} diff --git a/src/ReactiveUI/Interfaces/IScreen.cs b/src/ReactiveUI/Interfaces/IScreen.cs index 7f9a8446d0..bf1a95261a 100644 --- a/src/ReactiveUI/Interfaces/IScreen.cs +++ b/src/ReactiveUI/Interfaces/IScreen.cs @@ -6,13 +6,38 @@ namespace ReactiveUI; /// -/// IScreen represents any object that is hosting its own routing - -/// usually this object is your AppViewModel or MainWindow object. +/// Represents any object capable of hosting its own navigation stack via . /// +/// +/// +/// Most applications expose a single implementation of (for example a shell or app view model) +/// that owns the global router. Individual view models can accept an via constructor +/// injection so they can request navigation without directly referencing UI types. +/// +/// +/// +/// +/// ShowSettings { get; } +/// +/// public AppViewModel() +/// { +/// ShowSettings = ReactiveCommand.CreateFromObservable( +/// () => Router.Navigate.Execute(new SettingsViewModel(this))); +/// } +/// } +/// ]]> +/// +/// public interface IScreen { /// - /// Gets the Router associated with this Screen. + /// Gets the router associated with this screen. The router coordinates navigation requests for + /// all child view models attached to the screen. /// RoutingState Router { get; } -} \ No newline at end of file +} diff --git a/src/ReactiveUI/Interfaces/ISuspensionHost.cs b/src/ReactiveUI/Interfaces/ISuspensionHost.cs index f1a839a1a2..06343db148 100644 --- a/src/ReactiveUI/Interfaces/ISuspensionHost.cs +++ b/src/ReactiveUI/Interfaces/ISuspensionHost.cs @@ -21,6 +21,38 @@ namespace ReactiveUI; /// host operating system publishes. Subscribe to these events in order to /// handle app suspend / resume. /// +/// +/// +/// These observables abstract platform terms such as "Launching", "Activated", and "Closing" into a +/// consistent API so shared code can persist state without branching on specific UI stacks. Most +/// applications call RxApp.SuspensionHost.SetupDefaultSuspendResume() during startup to wire +/// default handlers, but the properties are public so advanced hosts can plug in their own monitoring. +/// +/// +/// represents the serialized model describing the last running session, while +/// can be configured to hydrate a fresh instance when a crash or first +/// launch occurs. +/// +/// +/// +/// +/// new ShellState(); +/// +/// suspensionHost.IsLaunchingNew.Subscribe(_ => +/// { +/// suspensionHost.AppState = suspensionHost.CreateNewAppState!(); +/// }); +/// +/// suspensionHost.ShouldPersistState.Subscribe(disposable => +/// { +/// storageService.Save((ShellState)suspensionHost.AppState!); +/// disposable.Dispose(); +/// }); +/// ]]> +/// +/// public interface ISuspensionHost : IReactiveObject { /// @@ -69,4 +101,4 @@ public interface ISuspensionHost : IReactiveObject /// the object other than it can be serialized. /// object? AppState { get; set; } -} \ No newline at end of file +} diff --git a/src/ReactiveUI/Interfaces/IViewFor.cs b/src/ReactiveUI/Interfaces/IViewFor.cs index 600320bcb6..51f2a64de2 100644 --- a/src/ReactiveUI/Interfaces/IViewFor.cs +++ b/src/ReactiveUI/Interfaces/IViewFor.cs @@ -6,29 +6,57 @@ namespace ReactiveUI; /// -/// This base class is mostly used by the Framework. Implement -/// instead. +/// Provides a non-generic abstraction over views so infrastructure can interact with instances. /// +/// +/// +/// Most application code implements instead of this interface directly. The non-generic +/// type exists so routing and binding helpers can store heterogeneous view references at runtime while still exposing +/// the property. +/// +/// public interface IViewFor : IActivatableView { /// - /// Gets or sets the View Model associated with the View. + /// Gets or sets the view model associated with the view. /// object? ViewModel { get; set; } } #pragma warning disable SA1402 // File may only contain a single type /// -/// Implement this interface on your Views to support Routing and Binding. +/// Implement this interface on views to participate in ReactiveUI routing, activation, and binding. /// -/// The type of ViewModel. +/// The type of view model presented by the view. +/// +/// +/// Views typically expose as a bindable property (dependency property in XAML, BindableProperty +/// in .NET MAUI, etc.). Implementations should also handle activation by calling WhenActivated inside the view to +/// manage subscriptions. +/// +/// +/// +/// +/// +/// { +/// public LoginView() +/// { +/// InitializeComponent(); +/// this.WhenActivated(disposables => +/// this.Bind(ViewModel, vm => vm.UserName, v => v.UserNameTextBox.Text) +/// .DisposeWith(disposables)); +/// } +/// } +/// ]]> +/// +/// public interface IViewFor : IViewFor #pragma warning restore SA1402 // File may only contain a single type where T : class { /// - /// Gets or sets the ViewModel corresponding to this specific View. This should be - /// a DependencyProperty if you're using XAML. + /// Gets or sets the strongly typed view model. Override this property to integrate with the platform's binding system. /// new T? ViewModel { get; set; } -} \ No newline at end of file +} diff --git a/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs b/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs index 512e5d2f61..d8e3b6fbaa 100644 --- a/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs +++ b/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs @@ -12,6 +12,24 @@ namespace ReactiveUI; /// notifications. This class can be created directly, but is more often created /// via the extension methods. /// +/// +/// +/// Use this helper when the value for a property is derived from one or more observable streams (for example, +/// WhenAnyValue). The helper subscribes to the source observable, tracks the latest value, and raises change +/// notifications through the supplied callbacks. +/// +/// +/// +/// +/// x.FirstName, x => x.LastName, +/// (first, last) => $"{first} {last}") +/// .ToProperty(this, x => x.FullName); +/// +/// public string FullName => _fullName.Value; +/// ]]> +/// +/// /// The type. public sealed class ObservableAsPropertyHelper : IHandleObservableErrors, IDisposable, IEnableLogger { diff --git a/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs b/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs index 45d3e5d71c..cfa641fb22 100644 --- a/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs +++ b/src/ReactiveUI/Platforms/apple-common/ViewModelViewHost.cs @@ -13,9 +13,34 @@ namespace ReactiveUI; /// -/// A control which will use Splat dependency injection to determine the View -/// to show. It uses. +/// A controller that resolves an implementation for the supplied and +/// hosts it as a child view controller. /// +/// +/// +/// is useful when a view is responsible for projecting an arbitrary view model instance +/// determined at runtime. The host listens for or contract changes, resolves a view via +/// , and swaps the child controller hierarchy accordingly. +/// +/// +/// Provide a controller to display placeholder UI while no view model is available, or set +/// to drive platform-specific view selection. +/// +/// +/// +/// +/// x.SelectedTheme); +/// ]]> +/// +/// #if NET6_0_OR_GREATER [RequiresDynamicCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp properties which require dynamic code generation")] [RequiresUnreferencedCode("ViewModelViewHost uses ReactiveUI extension methods and RxApp properties which may require unreferenced code")] @@ -43,11 +68,9 @@ public ViewModelViewHost() } /// - /// Gets or sets the view locator. + /// Gets or sets the used to resolve views for the current . Defaults + /// to if not provided. /// - /// - /// The view locator. - /// public IViewLocator? ViewLocator { get => _viewLocator; @@ -55,11 +78,8 @@ public IViewLocator? ViewLocator } /// - /// Gets or sets the default content. + /// Gets or sets the controller displayed when is . /// - /// - /// The default content. - /// public NSViewController? DefaultContent { get => _defaultContent; @@ -67,7 +87,7 @@ public NSViewController? DefaultContent } /// - /// Gets or sets the view model. + /// Gets or sets the view model whose view should be hosted. /// public object? ViewModel { @@ -76,7 +96,8 @@ public object? ViewModel } /// - /// Gets or sets the view contract observable. + /// Gets or sets an observable producing view contracts. Contracts allow multiple views to be registered for the same + /// view model but different display contexts. /// public IObservable? ViewContractObservable { @@ -85,7 +106,8 @@ public IObservable? ViewContractObservable } /// - /// Gets or sets the view contract. + /// Gets or sets the view contract used when resolving views. Assigning a contract produces a singleton observable + /// under the covers. /// public string? ViewContract { diff --git a/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs b/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs index 1550f9a30e..113567880b 100644 --- a/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs +++ b/src/ReactiveUI/Platforms/uikit-common/AutoSuspendHelper.cs @@ -12,13 +12,41 @@ namespace ReactiveUI; /// +/// Bridges iOS lifecycle notifications into so applications can persist and +/// restore state without manually wiring UIKit events. +/// +/// /// -/// AutoSuspend-based App Delegate. To use AutoSuspend with iOS, change your -/// AppDelegate to inherit from this class, then call: +/// Instantiate inside your and forward the +/// FinishedLaunching, OnActivated, and DidEnterBackground events to the helper. The helper updates +/// the shared observables and takes care of requesting background time when persisting +/// application state. /// -/// Locator.Current.GetService{ISuspensionHost}().SetupDefaultSuspendResume();. -/// This will get your suspension host. -/// +/// +/// +/// +/// +/// _autoSuspendHelper?.OnActivated(application); +/// +/// public override void DidEnterBackground(UIApplication application) => +/// _autoSuspendHelper?.DidEnterBackground(application); +/// } +/// ]]> +/// +/// #if NET6_0_OR_GREATER [RequiresDynamicCode("AutoSuspendHelper uses RxApp.SuspensionHost and reflection which require dynamic code generation")] [RequiresUnreferencedCode("AutoSuspendHelper uses RxApp.SuspensionHost and reflection which may require unreferenced code")] @@ -69,16 +97,14 @@ public AutoSuspendHelper(UIApplicationDelegate appDelegate) } /// - /// Gets the launch options. + /// Gets the launch options captured from the most recent call to . Keys are converted + /// to strings and values are stringified for convenience when hydrating state. /// - /// - /// The launch options. - /// public IDictionary? LaunchOptions { get; private set; } /// - /// Advances the finished launching observable. - /// Finisheds the launching. + /// Notifies the helper that was + /// invoked so it can propagate the observable. /// /// The application. /// The launch options. @@ -94,13 +120,14 @@ public void FinishedLaunching(UIApplication application, NSDictionary launchOpti } /// - /// Advances the on activated observable. + /// Notifies the helper that occurred. /// /// The application. public void OnActivated(UIApplication application) => _activated.OnNext(application); /// - /// Advances the enter background observable. + /// Notifies the helper that was raised so that + /// persistence can begin. /// /// The application. public void DidEnterBackground(UIApplication application) => _backgrounded.OnNext(application); @@ -113,7 +140,7 @@ public void Dispose() } /// - /// Disposes of any disposable entities within the class. + /// Releases managed resources held by the helper. /// /// If we are going to call Dispose methods on field items. protected virtual void Dispose(bool isDisposing) diff --git a/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs b/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs index 9ef1ef62d0..9a9de7e3f1 100644 --- a/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs +++ b/src/ReactiveUI/Platforms/uikit-common/RoutedViewHost.cs @@ -12,9 +12,36 @@ namespace ReactiveUI; /// -/// RoutedViewHost is a ReactiveNavigationController that monitors its RoutingState -/// and keeps the navigation stack in line with it. +/// A that observes a and mirrors its +/// navigation stack into UIKit. /// +/// +/// +/// Use inside iOS or Mac Catalyst applications to keep push/pop transitions aligned with +/// changes. The host resolves views via and updates titles using +/// so navigation remains consistent across app restarts. +/// +/// +/// Setting subscribes the host to , +/// , and collection change notifications. Manual calls to +/// and also update the +/// router so that imperative navigation cannot desynchronize the stacks. +/// +/// +/// +/// +/// x.SelectedContract) +/// }; +/// +/// shell.Router.Navigate.Execute(new DashboardViewModel(shell)).Subscribe(); +/// ]]> +/// +/// [SuppressMessage("Design", "CA1010: Implement generic IEnumerable", Justification = "UI Kit exposes IEnumerable")] #if NET6_0_OR_GREATER [RequiresDynamicCode("RoutedViewHost uses methods that require dynamic code generation")] @@ -132,7 +159,8 @@ public RoutedViewHost() } /// - /// Gets or sets the of the view model stack. + /// Gets or sets the responsible for driving the navigation stack. Assigning a router wires + /// the host up to all navigation observables. /// public RoutingState? Router { @@ -141,7 +169,8 @@ public RoutingState? Router } /// - /// Gets or sets the view contract observable. + /// Gets or sets the observable contract used when resolving views. When , the default contract + /// is applied. /// public IObservable? ViewContractObservable { @@ -150,7 +179,8 @@ public IObservable? ViewContractObservable } /// - /// Gets or sets the view locator. + /// Gets or sets the used to translate instances into + /// UIKit view controllers. Defaults to . /// public IViewLocator? ViewLocator { get; set; } diff --git a/src/ReactiveUI/Routing/RoutingState.cs b/src/ReactiveUI/Routing/RoutingState.cs index 5cf6cb720d..602bcc0e10 100644 --- a/src/ReactiveUI/Routing/RoutingState.cs +++ b/src/ReactiveUI/Routing/RoutingState.cs @@ -12,6 +12,46 @@ namespace ReactiveUI; /// RoutingState manages the ViewModel Stack and allows ViewModels to /// navigate to other ViewModels. /// +/// +/// +/// Use from an implementation to coordinate navigation in +/// multi-page applications. The stack works in a last-in-first-out fashion, enabling forward navigation via +/// and back navigation via . Consumers can observe +/// or to drive view presentation. +/// +/// +/// +/// +/// +/// Router.Navigate.Execute(new DetailsViewModel(this)).Subscribe(); +/// +/// public void GoBack() => +/// Router.NavigateBack.Execute(Unit.Default).Subscribe(); +/// } +/// +/// public partial class ShellView : ReactiveUserControl +/// { +/// public ShellView() +/// { +/// this.WhenActivated(disposables => +/// ViewModel!.Router.CurrentViewModel +/// .Subscribe(viewModel => contentHost.NavigateTo(viewModel)) +/// .DisposeWith(disposables)); +/// } +/// } +/// ]]> +/// +/// [DataContract] #if NET6_0_OR_GREATER [RequiresDynamicCode("RoutingState uses RxApp and ReactiveCommand which require dynamic code generation")] @@ -46,51 +86,48 @@ public RoutingState(IScheduler? scheduler = null) } /// - /// Gets or sets the current navigation stack, the last element in the - /// collection being the currently visible ViewModel. + /// Gets or sets the current navigation stack, with the last element representing the active view model. /// [DataMember] [JsonRequired] public ObservableCollection NavigationStack { get; set; } /// - /// Gets or sets a command which will navigate back to the previous element in the stack. + /// Gets or sets a command which will navigate back to the previous element in the stack and emits the new current view model. + /// The command can only execute when at least two view models exist in the stack. /// [IgnoreDataMember] [JsonIgnore] public ReactiveCommand NavigateBack { get; protected set; } /// - /// Gets or sets a command that navigates to the a new element in the stack - the Execute parameter - /// must be a ViewModel that implements IRoutableViewModel. + /// Gets or sets a command that adds a new element to the navigation stack. The command argument must implement + /// and the command emits the same instance once scheduling completes. /// [IgnoreDataMember] [JsonIgnore] public ReactiveCommand Navigate { get; protected set; } /// - /// Gets or sets a command that navigates to a new element and resets the navigation stack (i.e. the - /// new ViewModel will now be the only element in the stack) - the - /// Execute parameter must be a ViewModel that implements - /// IRoutableViewModel. + /// Gets or sets a command that replaces the entire navigation stack with the supplied view model, effectively resetting navigation history. /// [IgnoreDataMember] [JsonIgnore] public ReactiveCommand NavigateAndReset { get; protected set; } /// - /// Gets or sets the current view model which is to be shown for the Routing. + /// Gets or sets the observable that yields the currently active view model whenever the navigation stack changes. /// [IgnoreDataMember] [JsonIgnore] public IObservable CurrentViewModel { get; protected set; } /// - /// Gets or sets an observable which will signal when the Navigation changes. + /// Gets or sets an observable that signals detailed change sets for the navigation stack, enabling reactive views to animate push/pop operations. /// [IgnoreDataMember] [JsonIgnore] - public IObservable> NavigationChanged { get; protected set; } // TODO: Create Test + public IObservable> NavigationChanged { get; protected set; } [OnDeserialized] #if NET6_0_OR_GREATER diff --git a/src/ReactiveUI/Suspension/SuspensionHost.cs b/src/ReactiveUI/Suspension/SuspensionHost.cs index db4784c40b..f4ba203ef2 100644 --- a/src/ReactiveUI/Suspension/SuspensionHost.cs +++ b/src/ReactiveUI/Suspension/SuspensionHost.cs @@ -9,6 +9,28 @@ namespace ReactiveUI; /// A internal state setup by other classes for the different suspension state of a application. /// The user does not implement themselves but is often setup via the AutoSuspendHelper class. /// +/// +/// +/// backs and provides concrete observables that are wired up +/// by helpers such as . Platform hosts push their lifecycle notifications into the +/// ReplaySubject instances exposed here and view models subscribe through . +/// +/// +/// Consumers rarely instantiate this type directly; instead call RxApp.SuspensionHost to access the singleton. The +/// object is intentionally thread-safe via so events raised prior to subscription are +/// replayed to late subscribers. +/// +/// +/// +/// +/// new ShellState(); +/// suspensionHost.AppState = suspensionHost.CreateNewAppState(); +/// ]]> +/// +/// internal class SuspensionHost : ReactiveObject, ISuspensionHost, IDisposable { private readonly ReplaySubject> _isLaunchingNew = new(1);