Skip to content

Commit d0c5c8f

Browse files
authored
feature: Allow the ViewModel to be Null in BindValidation (#152)
* Allow the view model property to be null * Approve the new API * Verify that we support delayed view model initialization * Add Null guards to Android extensions
1 parent 030f250 commit d0c5c8f

File tree

7 files changed

+165
-50
lines changed

7 files changed

+165
-50
lines changed

src/ReactiveUI.Validation.AndroidSupport/Extensions/ViewForExtensions.cs

+52-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using ReactiveUI.Validation.ValidationBindings;
1616
using Splat;
1717

18+
// ReSharper disable once CheckNamespace
1819
namespace ReactiveUI.Validation.Extensions
1920
{
2021
/// <summary>
@@ -30,7 +31,7 @@ public static class ViewForExtensions
3031
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
3132
/// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
3233
/// <param name="view">IViewFor instance.</param>
33-
/// <param name="viewModel">ViewModel instance.</param>
34+
/// <param name="viewModel">ViewModel instance. Can be null, used for generic type resolution.</param>
3435
/// <param name="viewModelProperty">ViewModel property.</param>
3536
/// <param name="viewProperty">View property to bind the validation message.</param>
3637
/// <param name="formatter">
@@ -42,13 +43,28 @@ public static class ViewForExtensions
4243
[SuppressMessage("Design", "CA1801: Parameter unused", Justification = "Used for generic resolution.")]
4344
public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty>(
4445
this TView view,
45-
TViewModel viewModel,
46+
TViewModel? viewModel,
4647
Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
4748
TextInputLayout viewProperty,
4849
IValidationTextFormatter<string>? formatter = null)
4950
where TView : IViewFor<TViewModel>
5051
where TViewModel : class, IReactiveObject, IValidatableViewModel
5152
{
53+
if (view is null)
54+
{
55+
throw new ArgumentNullException(nameof(view));
56+
}
57+
58+
if (viewModelProperty is null)
59+
{
60+
throw new ArgumentNullException(nameof(viewModelProperty));
61+
}
62+
63+
if (viewProperty is null)
64+
{
65+
throw new ArgumentNullException(nameof(viewProperty));
66+
}
67+
5268
formatter ??= Locator.Current.GetService<IValidationTextFormatter<string>>() ??
5369
SingleLineFormatter.Default;
5470

@@ -67,7 +83,7 @@ public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty>(
6783
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
6884
/// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
6985
/// <param name="view">IViewFor instance.</param>
70-
/// <param name="viewModel">ViewModel instance.</param>
86+
/// <param name="viewModel">ViewModel instance. Can be null, used for generic type resolution.</param>
7187
/// <param name="viewModelProperty">ViewModel property.</param>
7288
/// <param name="viewProperty">View property to bind the validation message.</param>
7389
/// <param name="formatter">
@@ -81,13 +97,28 @@ public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty>(
8197
[SuppressMessage("Design", "CA1801: Parameter unused", Justification = "Used for generic resolution.")]
8298
public static IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty>(
8399
this TView view,
84-
TViewModel viewModel,
100+
TViewModel? viewModel,
85101
Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
86102
TextInputLayout viewProperty,
87103
IValidationTextFormatter<string>? formatter = null)
88104
where TView : IViewFor<TViewModel>
89105
where TViewModel : class, IReactiveObject, IValidatableViewModel
90106
{
107+
if (view is null)
108+
{
109+
throw new ArgumentNullException(nameof(view));
110+
}
111+
112+
if (viewModelProperty is null)
113+
{
114+
throw new ArgumentNullException(nameof(viewModelProperty));
115+
}
116+
117+
if (viewProperty is null)
118+
{
119+
throw new ArgumentNullException(nameof(viewProperty));
120+
}
121+
91122
formatter ??= Locator.Current.GetService<IValidationTextFormatter<string>>() ??
92123
SingleLineFormatter.Default;
93124

@@ -104,7 +135,7 @@ public static IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty
104135
/// <typeparam name="TView">IViewFor of TViewModel.</typeparam>
105136
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
106137
/// <param name="view">IViewFor instance.</param>
107-
/// <param name="viewModel">ViewModel instance.</param>
138+
/// <param name="viewModel">ViewModel instance. Can be null, used for generic type resolution.</param>
108139
/// <param name="viewModelHelperProperty">ViewModel's ValidationHelper property.</param>
109140
/// <param name="viewProperty">View property to bind the validation message.</param>
110141
/// <param name="formatter">
@@ -116,13 +147,28 @@ public static IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty
116147
[SuppressMessage("Design", "CA1801: Parameter unused", Justification = "Used for generic resolution.")]
117148
public static IDisposable BindValidation<TView, TViewModel>(
118149
this TView view,
119-
TViewModel viewModel,
150+
TViewModel? viewModel,
120151
Expression<Func<TViewModel?, ValidationHelper>> viewModelHelperProperty,
121152
TextInputLayout viewProperty,
122153
IValidationTextFormatter<string>? formatter = null)
123154
where TView : IViewFor<TViewModel>
124155
where TViewModel : class, IReactiveObject, IValidatableViewModel
125156
{
157+
if (view is null)
158+
{
159+
throw new ArgumentNullException(nameof(view));
160+
}
161+
162+
if (viewModelHelperProperty is null)
163+
{
164+
throw new ArgumentNullException(nameof(viewModelHelperProperty));
165+
}
166+
167+
if (viewProperty is null)
168+
{
169+
throw new ArgumentNullException(nameof(viewProperty));
170+
}
171+
126172
formatter ??= Locator.Current.GetService<IValidationTextFormatter<string>>() ??
127173
SingleLineFormatter.Default;
128174

src/ReactiveUI.Validation.AndroidX/Extensions/ViewForExtensions.cs

+52-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using ReactiveUI.Validation.ValidationBindings;
1616
using Splat;
1717

18+
// ReSharper disable once CheckNamespace
1819
namespace ReactiveUI.Validation.Extensions
1920
{
2021
/// <summary>
@@ -30,7 +31,7 @@ public static class ViewForExtensions
3031
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
3132
/// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
3233
/// <param name="view">IViewFor instance.</param>
33-
/// <param name="viewModel">ViewModel instance.</param>
34+
/// <param name="viewModel">ViewModel instance. Can be null, used for generic type resolution.</param>
3435
/// <param name="viewModelProperty">ViewModel property.</param>
3536
/// <param name="viewProperty">View property to bind the validation message.</param>
3637
/// <param name="formatter">
@@ -42,13 +43,28 @@ public static class ViewForExtensions
4243
[SuppressMessage("Design", "CA1801: Parameter unused", Justification = "Used for generic resolution.")]
4344
public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty>(
4445
this TView view,
45-
TViewModel viewModel,
46+
TViewModel? viewModel,
4647
Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
4748
TextInputLayout viewProperty,
4849
IValidationTextFormatter<string>? formatter = null)
4950
where TView : IViewFor<TViewModel>
5051
where TViewModel : class, IReactiveObject, IValidatableViewModel
5152
{
53+
if (view is null)
54+
{
55+
throw new ArgumentNullException(nameof(view));
56+
}
57+
58+
if (viewModelProperty is null)
59+
{
60+
throw new ArgumentNullException(nameof(viewModelProperty));
61+
}
62+
63+
if (viewProperty is null)
64+
{
65+
throw new ArgumentNullException(nameof(viewProperty));
66+
}
67+
5268
formatter ??= Locator.Current.GetService<IValidationTextFormatter<string>>() ??
5369
SingleLineFormatter.Default;
5470

@@ -67,7 +83,7 @@ public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty>(
6783
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
6884
/// <typeparam name="TViewModelProperty">ViewModel property type.</typeparam>
6985
/// <param name="view">IViewFor instance.</param>
70-
/// <param name="viewModel">ViewModel instance.</param>
86+
/// <param name="viewModel">ViewModel instance. Can be null, used for generic type resolution.</param>
7187
/// <param name="viewModelProperty">ViewModel property.</param>
7288
/// <param name="viewProperty">View property to bind the validation message.</param>
7389
/// <param name="formatter">
@@ -81,13 +97,28 @@ public static IDisposable BindValidation<TView, TViewModel, TViewModelProperty>(
8197
[SuppressMessage("Design", "CA1801: Parameter unused", Justification = "Used for generic resolution.")]
8298
public static IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty>(
8399
this TView view,
84-
TViewModel viewModel,
100+
TViewModel? viewModel,
85101
Expression<Func<TViewModel, TViewModelProperty>> viewModelProperty,
86102
TextInputLayout viewProperty,
87103
IValidationTextFormatter<string>? formatter = null)
88104
where TView : IViewFor<TViewModel>
89105
where TViewModel : class, IReactiveObject, IValidatableViewModel
90106
{
107+
if (view is null)
108+
{
109+
throw new ArgumentNullException(nameof(view));
110+
}
111+
112+
if (viewModelProperty is null)
113+
{
114+
throw new ArgumentNullException(nameof(viewModelProperty));
115+
}
116+
117+
if (viewProperty is null)
118+
{
119+
throw new ArgumentNullException(nameof(viewProperty));
120+
}
121+
91122
formatter ??= Locator.Current.GetService<IValidationTextFormatter<string>>() ??
92123
SingleLineFormatter.Default;
93124

@@ -104,7 +135,7 @@ public static IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty
104135
/// <typeparam name="TView">IViewFor of TViewModel.</typeparam>
105136
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
106137
/// <param name="view">IViewFor instance.</param>
107-
/// <param name="viewModel">ViewModel instance.</param>
138+
/// <param name="viewModel">ViewModel instance. Can be null, used for generic type resolution.</param>
108139
/// <param name="viewModelHelperProperty">ViewModel's ValidationHelper property.</param>
109140
/// <param name="viewProperty">View property to bind the validation message.</param>
110141
/// <param name="formatter">
@@ -116,13 +147,28 @@ public static IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty
116147
[SuppressMessage("Design", "CA1801: Parameter unused", Justification = "Used for generic resolution.")]
117148
public static IDisposable BindValidation<TView, TViewModel>(
118149
this TView view,
119-
TViewModel viewModel,
150+
TViewModel? viewModel,
120151
Expression<Func<TViewModel?, ValidationHelper>> viewModelHelperProperty,
121152
TextInputLayout viewProperty,
122153
IValidationTextFormatter<string>? formatter = null)
123154
where TView : IViewFor<TViewModel>
124155
where TViewModel : class, IReactiveObject, IValidatableViewModel
125156
{
157+
if (view is null)
158+
{
159+
throw new ArgumentNullException(nameof(view));
160+
}
161+
162+
if (viewModelHelperProperty is null)
163+
{
164+
throw new ArgumentNullException(nameof(viewModelHelperProperty));
165+
}
166+
167+
if (viewProperty is null)
168+
{
169+
throw new ArgumentNullException(nameof(viewProperty));
170+
}
171+
126172
formatter ??= Locator.Current.GetService<IValidationTextFormatter<string>>() ??
127173
SingleLineFormatter.Default;
128174

src/ReactiveUI.Validation.Tests/API/ApiApprovalTests.ValidationProject.net472.approved.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -284,19 +284,19 @@ namespace ReactiveUI.Validation.Extensions
284284
[System.Obsolete("This method is a part of ReactiveUI internals and will be removed from ReactiveUI" +
285285
".Validation public API soon.")]
286286
public static System.IDisposable BindToDirect<TTarget, TValue>(System.IObservable<TValue> @this, TTarget target, System.Linq.Expressions.Expression viewExpression) { }
287-
public static System.IDisposable BindValidation<TView, TViewModel, TViewProperty>(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
287+
public static System.IDisposable BindValidation<TView, TViewModel, TViewProperty>(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
288288
where TView : ReactiveUI.IViewFor<TViewModel>
289289
where TViewModel : class, ReactiveUI.IReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
290-
public static System.IDisposable BindValidation<TView, TViewModel, TViewProperty>(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel?, ReactiveUI.Validation.Helpers.ValidationHelper>> viewModelHelperProperty, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
290+
public static System.IDisposable BindValidation<TView, TViewModel, TViewProperty>(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel?, ReactiveUI.Validation.Helpers.ValidationHelper>> viewModelHelperProperty, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
291291
where TView : ReactiveUI.IViewFor<TViewModel>
292292
where TViewModel : class, ReactiveUI.IReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
293-
public static System.IDisposable BindValidation<TView, TViewModel, TViewModelProperty, TViewProperty>(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel, TViewModelProperty>> viewModelProperty, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
293+
public static System.IDisposable BindValidation<TView, TViewModel, TViewModelProperty, TViewProperty>(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel, TViewModelProperty>> viewModelProperty, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
294294
where TView : ReactiveUI.IViewFor<TViewModel>
295295
where TViewModel : class, ReactiveUI.IReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
296296
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
297297
[System.Obsolete("This method is no longer required, BindValidation now supports multiple validatio" +
298298
"ns.")]
299-
public static System.IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty, TViewProperty>(this TView view, TViewModel viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel, TViewModelProperty>> viewModelProperty, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
299+
public static System.IDisposable BindValidationEx<TView, TViewModel, TViewModelProperty, TViewProperty>(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression<System.Func<TViewModel, TViewModelProperty>> viewModelProperty, System.Linq.Expressions.Expression<System.Func<TView, TViewProperty>> viewProperty, ReactiveUI.Validation.Formatters.Abstractions.IValidationTextFormatter<string>? formatter = null)
300300
where TView : ReactiveUI.IViewFor<TViewModel>
301301
where TViewModel : class, ReactiveUI.IReactiveObject, ReactiveUI.Validation.Abstractions.IValidatableViewModel { }
302302
}

0 commit comments

Comments
 (0)