Skip to content

Commit 17eb0de

Browse files
Fix PR #4319 review comments: Shell navigation, Cancel canExecute, AOT-safe FromEventPattern, MAUI TFMs
- Add AppShell.xaml + AppShell.xaml.cs for MAUI Shell navigation - Update App.xaml.cs to use new AppShell() instead of NavigationPage - Fix LoginPage.xaml.cs: replace async void Subscribe with SelectMany/FromAsync + instance DisplayAlert - Fix all three LoginViewModel.cs: Cancel uses Login.IsExecuting as canExecute via Subject<Unit> pattern - Fix WPF LoginView.xaml.cs: use strongly-typed Observable.FromEventPattern overload - Fix MAUI csproj: add proper platform-specific MAUI TFMs + OutputType=Exe Agent-Logs-Url: https://github.com/reactiveui/ReactiveUI/sessions/93dee6e0-8644-4b85-996e-8a19bd5ccf27 Co-authored-by: glennawatson <5834289+glennawatson@users.noreply.github.com>
1 parent 9cedc01 commit 17eb0de

File tree

9 files changed

+78
-12
lines changed

9 files changed

+78
-12
lines changed

src/examples/ReactiveUI.Samples.Maui/App.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ public partial class App : Application
1717

1818
/// <inheritdoc/>
1919
protected override Window CreateWindow(IActivationState? activationState) =>
20-
new(new NavigationPage(new LoginPage()));
20+
new(new AppShell());
2121
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<Shell
3+
x:Class="ReactiveUI.Samples.Maui.AppShell"
4+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:local="clr-namespace:ReactiveUI.Samples.Maui"
7+
Shell.NavBarIsVisible="False">
8+
9+
<ShellContent
10+
ContentTemplate="{DataTemplate local:LoginPage}"
11+
Route="LoginPage" />
12+
13+
</Shell>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2025 .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.Samples.Maui;
7+
8+
/// <summary>
9+
/// Application shell providing Shell navigation for the MAUI sample.
10+
/// </summary>
11+
public partial class AppShell : Shell
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="AppShell"/> class.
15+
/// </summary>
16+
public AppShell() => InitializeComponent();
17+
}

src/examples/ReactiveUI.Samples.Maui/LoginPage.xaml.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ public LoginPage()
3434
.DisposeWith(d);
3535

3636
ViewModel.Login
37-
.Subscribe(async success =>
38-
await (Shell.Current?.DisplayAlert(
37+
.SelectMany(success => Observable.FromAsync(() =>
38+
DisplayAlert(
3939
success ? "Login Successful" : "Login Failed",
4040
success ? "Welcome!" : "Invalid credentials.",
41-
"OK") ?? Task.CompletedTask))
41+
"OK")))
42+
.Subscribe()
4243
.DisposeWith(d);
4344
});
4445
}

src/examples/ReactiveUI.Samples.Maui/LoginViewModel.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using System.Reactive.Concurrency;
7+
using System.Reactive.Subjects;
78

89
namespace ReactiveUI.Samples.Maui;
910

@@ -26,15 +27,20 @@ public LoginViewModel(IScheduler scheduler)
2627
vm => vm.Password,
2728
(user, pass) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(pass));
2829

29-
Cancel = ReactiveCommand.Create(() => { }, outputScheduler: scheduler);
30+
var cancelSubject = new Subject<Unit>();
3031

3132
Login = ReactiveCommand.CreateFromObservable(
3233
() => Observable
3334
.Return(Password is "secret")
3435
.Delay(TimeSpan.FromSeconds(1), scheduler)
35-
.TakeUntil(Cancel),
36+
.TakeUntil(cancelSubject),
3637
canLogin,
3738
scheduler);
39+
40+
Cancel = ReactiveCommand.Create(
41+
() => cancelSubject.OnNext(Unit.Default),
42+
Login.IsExecuting,
43+
scheduler);
3844
}
3945

4046
/// <summary>

src/examples/ReactiveUI.Samples.Maui/ReactiveUI.Samples.Maui.csproj

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net10.0</TargetFramework>
4+
<TargetFrameworks>net10.0-android</TargetFrameworks>
5+
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows')) or $([MSBuild]::IsOSPlatform('osx'))">$(TargetFrameworks);net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
6+
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
7+
<OutputType>Exe</OutputType>
58
<UseMaui>true</UseMaui>
69
<Nullable>enable</Nullable>
710
<ImplicitUsings>enable</ImplicitUsings>
@@ -10,6 +13,18 @@
1013
<IsAotCompatible>false</IsAotCompatible>
1114
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
1215
<EnableSingleFileAnalyzer>false</EnableSingleFileAnalyzer>
16+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
17+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
18+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
19+
<SupportedOSPlatformVersion Condition="$(TargetFramework.Contains('-windows'))">10.0.19041.0</SupportedOSPlatformVersion>
20+
<TargetPlatformMinVersion Condition="$(TargetFramework.Contains('-windows'))">10.0.19041.0</TargetPlatformMinVersion>
21+
</PropertyGroup>
22+
23+
<PropertyGroup Condition="$(TargetFramework.Contains('-windows'))">
24+
<WindowsPackageType>None</WindowsPackageType>
25+
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
26+
<PublishAppxPackage>false</PublishAppxPackage>
27+
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
1328
</PropertyGroup>
1429

1530
<ItemGroup>

src/examples/ReactiveUI.Samples.Winforms/LoginViewModel.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using System.Reactive.Concurrency;
7+
using System.Reactive.Subjects;
78

89
namespace ReactiveUI.Samples.Winforms;
910

@@ -26,15 +27,20 @@ public LoginViewModel(IScheduler scheduler)
2627
vm => vm.Password,
2728
(user, pass) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(pass));
2829

29-
Cancel = ReactiveCommand.Create(() => { }, outputScheduler: scheduler);
30+
var cancelSubject = new Subject<Unit>();
3031

3132
Login = ReactiveCommand.CreateFromObservable(
3233
() => Observable
3334
.Return(Password is "secret")
3435
.Delay(TimeSpan.FromSeconds(1), scheduler)
35-
.TakeUntil(Cancel),
36+
.TakeUntil(cancelSubject),
3637
canLogin,
3738
scheduler);
39+
40+
Cancel = ReactiveCommand.Create(
41+
() => cancelSubject.OnNext(Unit.Default),
42+
Login.IsExecuting,
43+
scheduler);
3844
}
3945

4046
/// <summary>

src/examples/ReactiveUI.Samples.Wpf/LoginView.xaml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ public LoginView()
3434
.DisposeWith(d);
3535

3636
// WPF PasswordBox doesn't support data binding, so marshal changes manually.
37-
Observable.FromEventPattern(Password, nameof(PasswordBox.PasswordChanged))
37+
Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
38+
h => Password.PasswordChanged += h,
39+
h => Password.PasswordChanged -= h)
3840
.Select(_ => Password.Password)
3941
.BindTo(this, v => v.ViewModel!.Password)
4042
.DisposeWith(d);

src/examples/ReactiveUI.Samples.Wpf/LoginViewModel.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using System.Reactive.Concurrency;
7+
using System.Reactive.Subjects;
78

89
namespace ReactiveUI.Samples.Wpf;
910

@@ -26,15 +27,20 @@ public LoginViewModel(IScheduler scheduler)
2627
vm => vm.Password,
2728
(user, pass) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(pass));
2829

29-
Cancel = ReactiveCommand.Create(() => { }, outputScheduler: scheduler);
30+
var cancelSubject = new Subject<Unit>();
3031

3132
Login = ReactiveCommand.CreateFromObservable(
3233
() => Observable
3334
.Return(Password is "secret")
3435
.Delay(TimeSpan.FromSeconds(1), scheduler)
35-
.TakeUntil(Cancel),
36+
.TakeUntil(cancelSubject),
3637
canLogin,
3738
scheduler);
39+
40+
Cancel = ReactiveCommand.Create(
41+
() => cancelSubject.OnNext(Unit.Default),
42+
Login.IsExecuting,
43+
scheduler);
3844
}
3945

4046
/// <summary>

0 commit comments

Comments
 (0)