Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 8 additions & 74 deletions src/ReactiveUI.Maui/Common/RoutedViewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
#if WINUI_TARGET
using System.Diagnostics.CodeAnalysis;
using Microsoft.UI.Xaml;
using ReactiveUI.Internal;
#if REACTIVE_SHIM
using ReactiveUI.Reactive.Maui.Internal;
#else
using ReactiveUI.Maui.Internal;
#endif
using ReactiveUI.Primitives;
using Splat;

#if REACTIVE_SHIM
Expand Down Expand Up @@ -58,78 +56,14 @@ public RoutedViewHost()
HorizontalContentAlignment = HorizontalAlignment.Stretch;
VerticalContentAlignment = VerticalAlignment.Stretch;

var platform = AppLocator.Current.GetService<IPlatformOperations>();
Func<string?> platformGetter = static () => default;

if (platform is null)
{
// NB: This used to be an error but WPF design mode can't read
// good or do other stuff good.
this.Log().Error(
"Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest "
+ "version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance.");
}
else
{
platformGetter = platform.GetOrientation;
}

ViewContractObservable = ModeDetector.InUnitTestRunner()
? Signal.Silent<string>()

// Replaces FromEvent(SizeChanged).StartWith(platformGetter()).DistinctUntilChanged().
: new StartWithObservable<string?>(
new FromEventObservable<string?>(onNext =>
{
SizeChangedEventHandler handler = (_, _) => onNext(platformGetter());
SizeChanged += handler;
return new ActionDisposable(() => SizeChanged -= handler);
}),
platformGetter())
.DistinctUntilChanged();

// Observe Router property changes using DependencyProperty (AOT-friendly)
var routerChanged = MauiReactiveHelpers.CreatePropertyValueObservable(
this,
nameof(Router),
RouterProperty,
() => Router);

// Observe ViewContractObservable property changes using DependencyProperty (AOT-friendly)
var viewContractObservableChanged = MauiReactiveHelpers.CreatePropertyValueObservable(
this,
nameof(ViewContractObservable),
ViewContractObservableProperty,
() => ViewContractObservable);

// Observe current view model from router. Replaces Where(...).SelectMany(r => r.CurrentViewModel).StartWith(null).
var currentViewModel = new StartWithObservable<IRoutableViewModel?>(
new KeepSignal<RoutingState?>(routerChanged, static router => router is not null)
.SelectMany(static router => router!.CurrentViewModel),
null);

// Flatten the ViewContractObservable observable-of-observable.
// Replaces SelectMany(x => x ?? Return(null)).Do(x => _viewContract = x).StartWith(ViewContract).
var viewContract = new StartWithObservable<string?>(
viewContractObservableChanged
.SelectMany(static x => x ?? Signal.Emit<string?>(null))
.Do(x => _viewContract = x),
ViewContract);

var viewModelAndContract = currentViewModel
.CombineLatest(
viewContract,
static (viewModel, contract) => (viewModel, contract));

// Subscribe directly without WhenActivated
// NB: The DistinctUntilChanged is useful because most views in
// WinRT will end up getting here twice - once for configuring
// the RoutedViewHost's ViewModel, and once on load via SizeChanged
_ = viewModelAndContract.DistinctUntilChanged()
.Subscribe(new DelegateObserver<(IRoutableViewModel? viewModel, string? contract)>(
ResolveViewForViewModel,
RxState.DefaultExceptionHandler.OnNext))
.DisposeWith(_subscriptions);
MauiReactiveHelpers.InitializeRoutedViewHost(
(this, this.Log(), observable => ViewContractObservable = observable),
(nameof(Router), RouterProperty, () => Router),
(nameof(ViewContractObservable), ViewContractObservableProperty, () => ViewContractObservable),
() => ViewContract,
contract => _viewContract = contract,
ResolveViewForViewModel,
_subscriptions);
}

/// <summary>Gets or sets the <see cref="RoutingState"/> of the view model stack.</summary>
Expand Down
94 changes: 14 additions & 80 deletions src/ReactiveUI.Maui/Common/RoutedViewHost{TViewModel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
#if WINUI_TARGET
using System.Diagnostics.CodeAnalysis;
using Microsoft.UI.Xaml;
using ReactiveUI.Internal;
#if REACTIVE_SHIM
using ReactiveUI.Reactive.Maui.Internal;
#else
using ReactiveUI.Maui.Internal;
#endif
using ReactiveUI.Primitives;
using Splat;

#if REACTIVE_SHIM
Expand Down Expand Up @@ -60,80 +58,22 @@ public RoutedViewHost()
HorizontalContentAlignment = HorizontalAlignment.Stretch;
VerticalContentAlignment = VerticalAlignment.Stretch;

var platform = AppLocator.Current.GetService<IPlatformOperations>();
Func<string?> platformGetter = static () => default;

if (platform is null)
{
// NB: This used to be an error but WPF design mode can't read
// good or do other stuff good.
this.Log().Error(
"Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest "
+ "version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance.");
}
else
{
platformGetter = platform.GetOrientation;
}

ViewContractObservable = ModeDetector.InUnitTestRunner()
? Signal.Silent<string>()

// Replaces FromEvent(SizeChanged).StartWith(platformGetter()).DistinctUntilChanged().
: new StartWithObservable<string?>(
new FromEventObservable<string?>(onNext =>
{
SizeChangedEventHandler handler = (_, _) => onNext(platformGetter());
SizeChanged += handler;
return new ActionDisposable(() => SizeChanged -= handler);
}),
platformGetter())
.DistinctUntilChanged();

// Observe Router property changes using DependencyProperty (AOT-friendly)
var routerChanged = MauiReactiveHelpers.CreatePropertyValueObservable(
this,
nameof(Router),
RouterProperty,
() => Router);

// Observe ViewContractObservable property changes using DependencyProperty (AOT-friendly)
var viewContractObservableChanged = MauiReactiveHelpers.CreatePropertyValueObservable(
this,
nameof(ViewContractObservable),
ViewContractObservableProperty,
() => ViewContractObservable);

// Observe current view model from router. Replaces Where(...).SelectMany(r => r.CurrentViewModel).StartWith(null).
var currentViewModel = new StartWithObservable<IRoutableViewModel?>(
new KeepSignal<RoutingState?>(routerChanged, static router => router is not null)
.SelectMany(static router => router!.CurrentViewModel),
null);

// Flatten the ViewContractObservable observable-of-observable.
// Replaces SelectMany(x => x ?? Return(null)).Do(x => _viewContract = x).StartWith(ViewContract).
var viewContract = new StartWithObservable<string?>(
viewContractObservableChanged
.SelectMany(static x => x ?? Signal.Emit<string?>(null))
.Do(x => _viewContract = x),
ViewContract);

var viewModelAndContract = currentViewModel
.CombineLatest(
viewContract,
static (viewModel, contract) => (viewModel, contract));

// Subscribe directly without WhenActivated
// NB: The DistinctUntilChanged is useful because most views in
// WinRT will end up getting here twice - once for configuring
// the RoutedViewHost's ViewModel, and once on load via SizeChanged
_ = viewModelAndContract.DistinctUntilChanged()
.Subscribe(new DelegateObserver<(IRoutableViewModel? viewModel, string? contract)>(
ResolveViewForViewModel,
RxState.DefaultExceptionHandler.OnNext))
.DisposeWith(_subscriptions);
MauiReactiveHelpers.InitializeRoutedViewHost(
(this, this.Log(), observable => ViewContractObservable = observable),
(nameof(Router), RouterProperty, () => Router),
(nameof(ViewContractObservable), ViewContractObservableProperty, () => ViewContractObservable),
() => ViewContract,
contract => _viewContract = contract,
ResolveViewForViewModel,
_subscriptions);
}

/// <summary>Gets or sets the view locator.</summary>
/// <value>
/// The view locator.
/// </value>
public IViewLocator? ViewLocator { get; set; }

/// <summary>Gets or sets the <see cref="RoutingState"/> of the view model stack.</summary>
public RoutingState Router
{
Expand Down Expand Up @@ -169,12 +109,6 @@ public string? ViewContract
}
}

/// <summary>Gets or sets the view locator.</summary>
/// <value>
/// The view locator.
/// </value>
public IViewLocator? ViewLocator { get; set; }

/// <summary>
/// Resolves and displays the view for the given view model and contract.
/// This method uses the generic ViewLocator.ResolveView{TViewModel} which is AOT-safe.
Expand Down
60 changes: 6 additions & 54 deletions src/ReactiveUI.Maui/Common/ViewModelViewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@

using System.Diagnostics.CodeAnalysis;
using Microsoft.UI.Xaml;
using ReactiveUI.Internal;
#if REACTIVE_SHIM
using ReactiveUI.Reactive.Maui.Internal;
#else
using ReactiveUI.Maui.Internal;
#endif
using ReactiveUI.Primitives;
using Splat;

#if REACTIVE_SHIM
Expand Down Expand Up @@ -58,58 +56,12 @@ public partial class ViewModelViewHost : TransitioningContentControl, IViewFor,
Justification = "The single-threaded UI control hands 'this' to MauiReactiveHelpers to observe its own dependency-property changes; it is never published to another thread.")]
public ViewModelViewHost()
{
var platform = AppLocator.Current.GetService<IPlatformOperations>();
Func<string?> platformGetter = static () => default;

if (platform is null)
{
// NB: This used to be an error but WPF design mode can't read
// good or do other stuff good.
this.Log().Error(
"Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest "
+ "version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance.");
}
else
{
platformGetter = platform.GetOrientation;
}

ViewContractObservable = ModeDetector.InUnitTestRunner()
? Signal.Silent<string>()

// Replaces FromEvent(SizeChanged).StartWith(platformGetter()).DistinctUntilChanged().
: new StartWithObservable<string?>(
new FromEventObservable<string?>(onNext =>
{
SizeChangedEventHandler handler = (_, _) => onNext(platformGetter());
SizeChanged += handler;
return new ActionDisposable(() => SizeChanged -= handler);
}),
platformGetter())
.DistinctUntilChanged();

// Observe ViewModel property changes without expression trees (AOT-friendly)
var viewModelChanged = MauiReactiveHelpers.CreatePropertyValueObservable(
this,
nameof(ViewModel),
ViewModelProperty,
() => ViewModel);

// Combine contract observable (recording the latest contract) with ViewModel changes.
var viewModelAndContract = ViewContractObservable.Do(x => _viewContract = x)
.CombineLatest(
viewModelChanged,
static (contract, vm) => (vm, contract));

// Subscribe directly without WhenActivated
_ = new ObserveOnObservable<string?>(ViewContractObservable, RxSchedulers.MainThreadScheduler)
.Subscribe(new DelegateObserver<string?>(x => _viewContract = x ?? string.Empty))
.DisposeWith(_subscriptions);

_ = viewModelAndContract.DistinctUntilChanged()
.Subscribe(new DelegateObserver<(object? ViewModel, string? Contract)>(
x => ResolveViewForViewModel(x.ViewModel, x.Contract)))
.DisposeWith(_subscriptions);
MauiReactiveHelpers.InitializeViewModelViewHost(
(this, this.Log(), observable => ViewContractObservable = observable),
(nameof(ViewModel), ViewModelProperty, () => ViewModel),
contract => _viewContract = contract,
ResolveViewForViewModel,
_subscriptions);
}

/// <summary>Gets or sets the view contract observable.</summary>
Expand Down
Loading
Loading