docs(examples): show default and Unsafe view hosts after #4505 (#4507) · reactiveui/ReactiveUI@bbe16fa · GitHub
Skip to content

Commit bbe16fa

Browse files
authored
docs(examples): show default and Unsafe view hosts after #4505 (#4507)
## Summary **The documentation examples show the default view hosts finding generated and `Map` views, and use each `...Unsafe` twin only for a view registered in the service locator alone.** - **WinUI: the maintenance panel uses the default hosts.** `SensorMaintenanceView` is added with `Map`, and `RoutedViewHost` and `ViewModelViewHost` now show it. - **WinUI: a radar panel shows the case the twins exist for.** `RadarImageView` is registered only with the service locator and is hosted by `RoutedViewHostUnsafe` and `ViewModelViewHostUnsafe`. `WinUIStartupExamples.MapAViewFromTheServiceLocator` shows the AOT-safe alternative, `CreateMappingBuilder().MapFromServiceLocator<TViewModel, IViewFor<TViewModel>>()`. - **WPF: a notice board hosts a service-locator-only view** through `RoutedViewHostUnsafe` and `ViewModelViewHostUnsafe`, and `WpfBuilderExtensionsExamples.AddTheUnsafeTemplateHook` registers `AutoDataTemplateBindingHookUnsafe`. - **WinForms and MAUI: new `ServiceLocatorViewExamples`.** Each shows its two twins against a service-locator-only view, and `MapFromServiceLocator` letting the default host find the same view. - **MAUI: the `Wide` contract view carries `[ViewContract("Wide")]`.** The contract examples registered it only with the service locator, which the default host does not ask. - **Android: `FirstLessonPeekHost` derives from `ReactiveViewHostUnsafe<LessonViewModel>`** to show auto-wireup on a host with a view model, next to `LessonCountBadgeHost` on `LayoutViewHostUnsafe`. ## Why **#4505 made every default view host AOT safe, so the examples that worked around #4502 and the examples that registered views only in the service locator needed updating.** - The WinUI maintenance panel used the Unsafe twins only because the default hosts missed `Map` views. - Two MAUI contract examples failed their output check, because the default `ViewModelViewHost` no longer asks the service locator. ## Breaking changes None. Examples only. ## How this was verified **Every page project builds in Release with warnings as errors, and the output checks pass.** - The WPF, WinForms and WinUI projects were built and run with `--smoke` on a real Windows machine; the Android example ran on an emulator. ## Notes for the reviewer **Start with `platform-winui/MainWindow.cs` and the three `ServiceLocatorViewExamples`/notice-board additions; the rest is new view and view model types.** - The service-locator-only views carry `[ExcludeFromViewRegistration]` to stand in for a view from a library built without the source generator. - The WinForms `--smoke` run stops at the loan confirmation with an `ObjectDisposedException`: `RoutedControlHost` disposes the catalog view, which disposes the `BookCards` labels its view model owns, and the next catalog view binds those disposed labels. It fails the same way on the commit before #4505, so it is not caused by this change and is left out of scope. ## Checklist - [x] I have read the [Contribute guide](https://www.reactiveui.net/contribute/index.html) - [x] The PR title follows [Conventional Commits](https://www.conventionalcommits.org/) - [x] Tests cover this change, or the summary says why they do not - [ ] New or changed public API has XML documentation
1 parent 6def5a6 commit bbe16fa

33 files changed

Lines changed: 632 additions & 56 deletions
Lines changed: 37 additions & 0 deletions

‎src/examples/Documentation/Pages/platform-android/MainActivity.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ protected override async void OnCreate(Bundle? savedInstanceState)
6363
badgeHost.SetLessonCount(ViewModel.Lessons.Count);
6464
BadgeContainer!.AddView(badgeHost.ToBadgeView());
6565

66+
// Auto-wireup fills PeekSubjectLabel by name, so this peek's constructor needs no FindViewById call.
67+
FirstLessonPeekHost firstLessonPeek = new(this, PeeksRow!) { ViewModel = ViewModel.Lessons[0] };
68+
PeeksRow!.AddView(firstLessonPeek.View);
69+
TimetableLog.Info($"Auto-wired peek shows: {firstLessonPeek.PeekSubjectLabel?.Text}.");
70+
6671
// The 3-arg constructor defaults attachToRoot to false, so this peek is added with an explicit AddView call.
6772
LessonPeekHost secondLessonPeek = new(this, PeeksRow!) { ViewModel = ViewModel.Lessons[1] };
6873
PeeksRow!.AddView(secondLessonPeek.View);

‎src/examples/Documentation/Pages/platform-maui/Program.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@
5252

5353
ViewModelViewHostExamples.GenericHostTypesTheViewModelProperty();
5454

55+
ServiceLocatorViewExamples.ViewModelViewHostUnsafeFindsTheView();
56+
57+
await ServiceLocatorViewExamples.RoutedViewHostUnsafePushesThePage();
58+
59+
ServiceLocatorViewExamples.MapFromServiceLocatorLetsTheDefaultHostFindTheView();
60+
5561
AutoSuspendHelperExamples.LifecycleMethodsRelayToTheSuspensionHost();
5662

5763
AutoSuspendHelperExamples.UntimelyDemiseIsAStaticSignal();

‎src/examples/Documentation/Pages/platform-maui/RecipeListWideContentView.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
namespace ReactiveUI.Documentation.PlatformMaui;
99

10-
/// <summary>The view <see cref="ViewModelViewHost"/> shows for <see cref="RecipeListViewModel"/> under the "Wide" contract, for a tablet in landscape.</summary>
10+
/// <summary>
11+
/// The view <see cref="ViewModelViewHost"/> shows for <see cref="RecipeListViewModel"/> under the "Wide" contract, for a
12+
/// tablet in landscape. <see cref="ViewContractAttribute"/> puts it in the generated view lookup under that contract.
13+
/// </summary>
14+
[ViewContract("Wide")]
1115
[System.Diagnostics.DebuggerDisplay("RecipeListWideContentView")]
1216
public sealed class RecipeListWideContentView : ReactiveContentView<RecipeListViewModel>;

‎src/examples/Documentation/Pages/platform-maui/RecipeRoutedViewHostOfDetail.cs‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
namespace ReactiveUI.Documentation.PlatformMaui;
1010

1111
/// <summary>
12-
/// A <see cref="RoutedViewHost{TViewModel}"/> fixed to <see cref="RecipeDetailViewModel"/>. Resolving its page never
13-
/// uses reflection, unlike the non-generic <see cref="RoutedViewHost"/>, so it is safe to trim and to publish as
14-
/// NativeAOT.
12+
/// A <see cref="RoutedViewHost{TViewModel}"/> fixed to <see cref="RecipeDetailViewModel"/>. It resolves its page through
13+
/// that compile-time type, with no reflection, so it is safe to trim and to publish as NativeAOT.
1514
/// </summary>
1615
[System.Diagnostics.DebuggerDisplay("RecipeRoutedViewHostOfDetail")]
1716
public sealed class RecipeRoutedViewHostOfDetail : RoutedViewHost<RecipeDetailViewModel>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using ReactiveUI.Maui;
7+
using Splat;
8+
9+
namespace ReactiveUI.Documentation.PlatformMaui;
10+
11+
/// <summary>
12+
/// Shows the two ways to host a view registered only with the service locator: the Unsafe twin of each host, or a
13+
/// <c>MapFromServiceLocator</c> entry that the default hosts read.
14+
/// </summary>
15+
public static class ServiceLocatorViewExamples
16+
{
17+
/// <summary>The default <see cref="ViewModelViewHost"/> throws for this view; <see cref="ViewModelViewHostUnsafe"/> finds it.</summary>
18+
public static void ViewModelViewHostUnsafeFindsTheView()
19+
{
20+
AppLocator.CurrentMutable.Register<IViewFor<ShoppingListViewModel>>(static () => new ShoppingListView());
21+
ShoppingListViewModel shopping = new(new RecipeBookScreen());
22+
23+
ViewModelViewHost host = new();
24+
try
25+
{
26+
host.ViewModel = shopping;
27+
}
28+
catch (InvalidOperationException exception)
29+
{
30+
Console.WriteLine(exception.GetType().Name);
31+
}
32+
33+
ViewModelViewHostUnsafe unsafeHost = new() { ViewModel = shopping };
34+
Console.WriteLine(unsafeHost.Content?.GetType().Name);
35+
36+
// Output:
37+
// InvalidOperationException
38+
// ShoppingListView
39+
}
40+
41+
/// <summary><see cref="RoutedViewHostUnsafe"/> pushes a page registered only with the service locator.</summary>
42+
/// <returns>A task that completes once the navigation has run.</returns>
43+
public static async Task RoutedViewHostUnsafePushesThePage()
44+
{
45+
RecipeBookScreen screen = new();
46+
AppLocator.CurrentMutable.RegisterConstant<IScreen>(screen);
47+
AppLocator.CurrentMutable.Register<IViewFor<ShoppingListViewModel>>(static () => new ShoppingListPage());
48+
49+
RoutedViewHostUnsafe host = new();
50+
_ = await screen.Router.Navigate.Execute(new ShoppingListViewModel(screen));
51+
52+
Console.WriteLine(host.Navigation.NavigationStack[0].GetType().Name);
53+
54+
// Output:
55+
// ShoppingListPage
56+
}
57+
58+
/// <summary>
59+
/// <c>MapFromServiceLocator</c> adds the service locator's view to the view locator's <c>Map</c> entries, so the
60+
/// default <see cref="ViewModelViewHost"/> finds it and the app stays safe to compile ahead of time.
61+
/// </summary>
62+
public static void MapFromServiceLocatorLetsTheDefaultHostFindTheView()
63+
{
64+
AppLocator.CurrentMutable.Register<IViewFor<ShoppingListViewModel>>(static () => new ShoppingListView());
65+
DefaultViewLocator locator = new();
66+
_ = locator.CreateMappingBuilder().MapFromServiceLocator<ShoppingListViewModel, IViewFor<ShoppingListViewModel>>();
67+
68+
ViewModelViewHost host = new() { ViewLocator = locator, ViewModel = new ShoppingListViewModel(new RecipeBookScreen()) };
69+
70+
Console.WriteLine(host.Content?.GetType().Name);
71+
72+
// Output:
73+
// ShoppingListView
74+
}
75+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using ReactiveUI.Maui;
7+
8+
namespace ReactiveUI.Documentation.PlatformMaui;
9+
10+
/// <summary>
11+
/// The shopping plug-in's page for <see cref="ShoppingListViewModel"/>, pushed by a routed host. Like
12+
/// <see cref="ShoppingListView"/>, it is registered with the service locator only.
13+
/// </summary>
14+
[ExcludeFromViewRegistration]
15+
[System.Diagnostics.DebuggerDisplay("ShoppingListPage")]
16+
public sealed class ShoppingListPage : ReactiveContentPage<ShoppingListViewModel>;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using ReactiveUI.Maui;
7+
8+
namespace ReactiveUI.Documentation.PlatformMaui;
9+
10+
/// <summary>
11+
/// The shopping plug-in's view for <see cref="ShoppingListViewModel"/>. The plug-in registers it with the service
12+
/// locator only. <see cref="ExcludeFromViewRegistrationAttribute"/> keeps it out of the generated view lookup, the way a
13+
/// view from a library built without the source generator is.
14+
/// </summary>
15+
[ExcludeFromViewRegistration]
16+
[System.Diagnostics.DebuggerDisplay("ShoppingListView")]
17+
public sealed class ShoppingListView : ReactiveContentView<ShoppingListViewModel>;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2009-2026 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
namespace ReactiveUI.Documentation.PlatformMaui;
7+
8+
/// <summary>
9+
/// The shopping list for the recipes a cook picked. It comes from a shopping plug-in whose views are registered only
10+
/// with the service locator.
11+
/// </summary>
12+
/// <param name="hostScreen">The window the list is shown in.</param>
13+
[System.Diagnostics.DebuggerDisplay("{UrlPathSegment}")]
14+
public sealed class ShoppingListViewModel(IScreen hostScreen) : ReactiveObject, IRoutableViewModel
15+
{
16+
/// <inheritdoc/>
17+
public string UrlPathSegment => "shopping-list";
18+
19+
/// <inheritdoc/>
20+
public IScreen HostScreen { get; } = hostScreen;
21+
}

‎src/examples/Documentation/Pages/platform-maui/ViewModelViewHostExamples.cs‎

Lines changed: 1 addition & 3 deletions

0 commit comments

Comments
 (0)