Skip to content

Commit

Permalink
Update ReactiveProperty for multiple subscribers (#3953)
Browse files Browse the repository at this point in the history
<!-- Please be sure to read the
[Contribute](https://github.com/reactiveui/reactiveui#contribute)
section of the README -->

**What kind of change does this PR introduce?**
<!-- Bug fix, feature, docs update, ... -->

fix for #3935 

**What is the current behavior?**
<!-- You can also link to an open issue here. -->

#3935 

**What is the new behavior?**
<!-- If this is a feature change -->

ReactiveProperty updated to handle multiple subscribers

**What might this PR break?**

None expected, core function still retained

**Please check if the PR fulfills these requirements**
- [x] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)

**Other information**:
  • Loading branch information
ChrisPulman authored Feb 1, 2025
1 parent a100b0b commit a2a4797
Show file tree
Hide file tree
Showing 20 changed files with 662 additions and 442 deletions.
22 changes: 12 additions & 10 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<PropertyGroup>
<SplatVersion>15.2.22</SplatVersion>
<SplatVersion>15.3.1</SplatVersion>
<XamarinAndroidXCoreVersion>1.13.1.4</XamarinAndroidXCoreVersion>
<XamarinAndroidXLifecycleLiveDataVersion>2.8.4.1</XamarinAndroidXLifecycleLiveDataVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
<PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="FluentAssertions" Version="8.0.0" />
<PackageVersion Include="DynamicData" Version="9.1.1" />
<PackageVersion Include="FluentAssertions" Version="8.0.1" />
<PackageVersion Include="JetBrains.DotMemoryUnit" Version="3.2.20220510" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
Expand All @@ -20,12 +22,12 @@
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageVersion Include="Mocks.Maui" Version="1.1.8" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.7.115" />
<PackageVersion Include="PublicApiGenerator" Version="11.3.0" />
<PackageVersion Include="PublicApiGenerator" Version="11.4.1" />
<PackageVersion Include="Reactive.Wasm" Version="2.1.2" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.12.10" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.12.11" />
<PackageVersion Include="Splat" Version="$(SplatVersion)" />
<PackageVersion Include="Splat.Autofac" Version="$(SplatVersion)" />
<PackageVersion Include="Splat.Drawing" Version="$(SplatVersion)" />
<PackageVersion Include="Splat.Drawing" Version="15.2.22" />
<PackageVersion Include="Splat.DryIoc" Version="$(SplatVersion)" />
<PackageVersion Include="Splat.Ninject" Version="$(SplatVersion)" />
<PackageVersion Include="stylecop.analyzers" Version="1.2.0-beta.556" />
Expand All @@ -38,11 +40,11 @@
<PackageVersion Include="xunit.runner.console" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
<PackageVersion Include="Xunit.StaFact" Version="1.2.69" />
<PackageVersion Include="Xamarin.AndroidX.Core" Version="1.15.0.1" />
<PackageVersion Include="Xamarin.AndroidX.Core" Version="$(XamarinAndroidXCoreVersion)" />
<PackageVersion Include="Xamarin.AndroidX.Preference" Version="1.2.1.9" />
<PackageVersion Include="Xamarin.AndroidX.Legacy.Support.Core.UI" Version="1.0.0.29" />
<PackageVersion Include="Xamarin.Google.Android.Material" Version="1.11.0.2" />
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.8.7.1" />
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="$(XamarinAndroidXLifecycleLiveDataVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(UseMaui)' != 'true'">
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
Expand All @@ -55,8 +57,8 @@
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net9'))">
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.1" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="9.0.30" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.30" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net8'))">
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="8.0.12" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ namespace ReactiveUI
public class CommandBinderImplementation : Splat.IEnableLogger
{
public CommandBinderImplementation() { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public ReactiveUI.IReactiveBinding<TView, TProp> BindCommand<TView, TViewModel, TProp, TControl, TParam>(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression<System.Func<TViewModel, TProp?>> vmProperty, System.Linq.Expressions.Expression<System.Func<TView, TControl>> controlProperty, System.IObservable<TParam?> withParameter, string? toEvent = null)
where TView : class, ReactiveUI.IViewFor
where TViewModel : class
where TProp : System.Windows.Input.ICommand { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public ReactiveUI.IReactiveBinding<TView, TProp> BindCommand<TView, TViewModel, TProp, TControl, TParam>(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression<System.Func<TViewModel, TProp?>> vmProperty, System.Linq.Expressions.Expression<System.Func<TView, TControl>> controlProperty, System.Linq.Expressions.Expression<System.Func<TViewModel, TParam?>> withParameter, string? toEvent = null)
where TView : class, ReactiveUI.IViewFor
where TViewModel : class
Expand All @@ -108,6 +112,8 @@ namespace ReactiveUI
public static System.Collections.Generic.IComparer<T> ThenByDescending<T, TValue>(this System.Collections.Generic.IComparer<T>? parent, System.Func<T, TValue> selector) { }
public static System.Collections.Generic.IComparer<T> ThenByDescending<T, TValue>(this System.Collections.Generic.IComparer<T>? parent, System.Func<T, TValue> selector, System.Collections.Generic.IComparer<TValue> comparer) { }
}
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public class ComponentModelTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger
{
public ComponentModelTypeConverter() { }
Expand Down Expand Up @@ -154,6 +160,8 @@ namespace ReactiveUI
public static class DependencyResolverMixins
{
public static void InitializeReactiveUI(this Splat.IMutableDependencyResolver resolver, params ReactiveUI.RegistrationNamespace[] registrationNamespaces) { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public static void RegisterViewsForViewModels(this Splat.IMutableDependencyResolver resolver, System.Reflection.Assembly assembly) { }
}
public class DoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger
Expand Down Expand Up @@ -766,6 +774,8 @@ namespace ReactiveUI
}
public static class ReactivePropertyMixins
{
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public static ReactiveUI.ReactiveProperty<T> AddValidation<T>(this ReactiveUI.ReactiveProperty<T> self, System.Linq.Expressions.Expression<System.Func<ReactiveUI.ReactiveProperty<T>?>> selfSelector) { }
public static System.IObservable<string?> ObserveValidationErrors<T>(this ReactiveUI.ReactiveProperty<T> self) { }
}
Expand Down Expand Up @@ -874,19 +884,19 @@ namespace ReactiveUI
public RoutingState(System.Reactive.Concurrency.IScheduler? scheduler = null) { }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public System.IObservable<ReactiveUI.IRoutableViewModel> CurrentViewModel { get; set; }
public System.IObservable<ReactiveUI.IRoutableViewModel> CurrentViewModel { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> Navigate { get; set; }
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> Navigate { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> NavigateAndReset { get; set; }
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> NavigateAndReset { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<System.Reactive.Unit, ReactiveUI.IRoutableViewModel> NavigateBack { get; set; }
public ReactiveUI.ReactiveCommand<System.Reactive.Unit, ReactiveUI.IRoutableViewModel> NavigateBack { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public System.IObservable<DynamicData.IChangeSet<ReactiveUI.IRoutableViewModel>> NavigationChanged { get; set; }
public System.IObservable<DynamicData.IChangeSet<ReactiveUI.IRoutableViewModel>> NavigationChanged { get; protected set; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonRequired]
public System.Collections.ObjectModel.ObservableCollection<ReactiveUI.IRoutableViewModel> NavigationStack { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ namespace ReactiveUI
public class CommandBinderImplementation : Splat.IEnableLogger
{
public CommandBinderImplementation() { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public ReactiveUI.IReactiveBinding<TView, TProp> BindCommand<TView, TViewModel, TProp, TControl, TParam>(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression<System.Func<TViewModel, TProp?>> vmProperty, System.Linq.Expressions.Expression<System.Func<TView, TControl>> controlProperty, System.IObservable<TParam?> withParameter, string? toEvent = null)
where TView : class, ReactiveUI.IViewFor
where TViewModel : class
where TProp : System.Windows.Input.ICommand { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public ReactiveUI.IReactiveBinding<TView, TProp> BindCommand<TView, TViewModel, TProp, TControl, TParam>(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression<System.Func<TViewModel, TProp?>> vmProperty, System.Linq.Expressions.Expression<System.Func<TView, TControl>> controlProperty, System.Linq.Expressions.Expression<System.Func<TViewModel, TParam?>> withParameter, string? toEvent = null)
where TView : class, ReactiveUI.IViewFor
where TViewModel : class
Expand All @@ -108,6 +112,8 @@ namespace ReactiveUI
public static System.Collections.Generic.IComparer<T> ThenByDescending<T, TValue>(this System.Collections.Generic.IComparer<T>? parent, System.Func<T, TValue> selector) { }
public static System.Collections.Generic.IComparer<T> ThenByDescending<T, TValue>(this System.Collections.Generic.IComparer<T>? parent, System.Func<T, TValue> selector, System.Collections.Generic.IComparer<TValue> comparer) { }
}
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public class ComponentModelTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger
{
public ComponentModelTypeConverter() { }
Expand Down Expand Up @@ -154,6 +160,8 @@ namespace ReactiveUI
public static class DependencyResolverMixins
{
public static void InitializeReactiveUI(this Splat.IMutableDependencyResolver resolver, params ReactiveUI.RegistrationNamespace[] registrationNamespaces) { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public static void RegisterViewsForViewModels(this Splat.IMutableDependencyResolver resolver, System.Reflection.Assembly assembly) { }
}
public class DoubleToStringTypeConverter : ReactiveUI.IBindingTypeConverter, Splat.IEnableLogger
Expand Down Expand Up @@ -766,6 +774,8 @@ namespace ReactiveUI
}
public static class ReactivePropertyMixins
{
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The method uses reflection and will not work in AOT environments.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The method uses reflection and will not work in AOT environments.")]
public static ReactiveUI.ReactiveProperty<T> AddValidation<T>(this ReactiveUI.ReactiveProperty<T> self, System.Linq.Expressions.Expression<System.Func<ReactiveUI.ReactiveProperty<T>?>> selfSelector) { }
public static System.IObservable<string?> ObserveValidationErrors<T>(this ReactiveUI.ReactiveProperty<T> self) { }
}
Expand Down Expand Up @@ -874,19 +884,19 @@ namespace ReactiveUI
public RoutingState(System.Reactive.Concurrency.IScheduler? scheduler = null) { }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public System.IObservable<ReactiveUI.IRoutableViewModel> CurrentViewModel { get; set; }
public System.IObservable<ReactiveUI.IRoutableViewModel> CurrentViewModel { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> Navigate { get; set; }
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> Navigate { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> NavigateAndReset { get; set; }
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> NavigateAndReset { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<System.Reactive.Unit, ReactiveUI.IRoutableViewModel> NavigateBack { get; set; }
public ReactiveUI.ReactiveCommand<System.Reactive.Unit, ReactiveUI.IRoutableViewModel> NavigateBack { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public System.IObservable<DynamicData.IChangeSet<ReactiveUI.IRoutableViewModel>> NavigationChanged { get; set; }
public System.IObservable<DynamicData.IChangeSet<ReactiveUI.IRoutableViewModel>> NavigationChanged { get; protected set; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonRequired]
public System.Collections.ObjectModel.ObservableCollection<ReactiveUI.IRoutableViewModel> NavigationStack { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,19 +856,19 @@ namespace ReactiveUI
public RoutingState(System.Reactive.Concurrency.IScheduler? scheduler = null) { }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public System.IObservable<ReactiveUI.IRoutableViewModel> CurrentViewModel { get; set; }
public System.IObservable<ReactiveUI.IRoutableViewModel> CurrentViewModel { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> Navigate { get; set; }
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> Navigate { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> NavigateAndReset { get; set; }
public ReactiveUI.ReactiveCommand<ReactiveUI.IRoutableViewModel, ReactiveUI.IRoutableViewModel> NavigateAndReset { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public ReactiveUI.ReactiveCommand<System.Reactive.Unit, ReactiveUI.IRoutableViewModel> NavigateBack { get; set; }
public ReactiveUI.ReactiveCommand<System.Reactive.Unit, ReactiveUI.IRoutableViewModel> NavigateBack { get; protected set; }
[System.Runtime.Serialization.IgnoreDataMember]
[System.Text.Json.Serialization.JsonIgnore]
public System.IObservable<DynamicData.IChangeSet<ReactiveUI.IRoutableViewModel>> NavigationChanged { get; set; }
public System.IObservable<DynamicData.IChangeSet<ReactiveUI.IRoutableViewModel>> NavigationChanged { get; protected set; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonRequired]
public System.Collections.ObjectModel.ObservableCollection<ReactiveUI.IRoutableViewModel> NavigationStack { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ namespace ReactiveUI.Winforms
[System.ComponentModel.Category("ReactiveUI")]
[System.ComponentModel.Description("The Current View")]
[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)]
public object? Content { get; set; }
public object? Content { get; protected set; }
public System.Windows.Forms.Control? CurrentView { get; }
[System.ComponentModel.Category("ReactiveUI")]
[System.ComponentModel.Description("The default control when no viewmodel is specified")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ namespace ReactiveUI.Winforms
[System.ComponentModel.Category("ReactiveUI")]
[System.ComponentModel.Description("The Current View")]
[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)]
public object? Content { get; set; }
public object? Content { get; protected set; }
public System.Windows.Forms.Control? CurrentView { get; }
[System.ComponentModel.Category("ReactiveUI")]
[System.ComponentModel.Description("The default control when no viewmodel is specified")]
Expand Down
Loading

0 comments on commit a2a4797

Please sign in to comment.