Skip to content

Commit c0dc37c

Browse files
refactor: Deprecate obsolete MAUI Cell controls and introduce CollectionView-compatible alternatives (#4146)
## Deprecate obsolete MAUI Cell controls and introduce CollectionView-compatible alternatives This task implements the deprecation of legacy `Reactive*Cell` controls and introduces new `ReactiveContentView`-based components compatible with CollectionView, aligning with .NET MAUI's official deprecation of ListView starting in .NET 10. ### ✅ Implementation Complete - All Compile Errors Fixed - [x] **Step 1: Investigation and Inventory** - Located all Reactive*Cell files - [x] **Step 2: Mark Existing Cells as Obsolete** - Add `[Obsolete]` attributes with helpful migration messages - [x] **Step 3: Create New ReactiveContentView-based Components** - ReactiveTextItemView and ReactiveImageItemView - [x] **Step 4: Fix Compile Errors and Verify Build** - ✅ **Solution builds successfully with no errors** ### 🔧 Latest Fix: Compile Error Resolution **Issues Resolved:** - ✅ Added missing `using Microsoft.Maui.Graphics;` directive for `Color` types - ✅ Simplified `Padding` properties to use numeric values instead of `Thickness` objects - ✅ Removed `Aspect.AspectFill` property to avoid type dependencies - ✅ Changed deprecated `LayoutOptions.FillAndExpand` to `LayoutOptions.Fill` **Build Verification:** - ✅ Full ReactiveUI.sln builds successfully with .NET 9.0 and 10.0 - ✅ ReactiveUI.Maui.Tests pass - ✅ No warnings or errors ### 📋 Complete Changes Summary #### Obsolete Attributes Added All legacy `Reactive*Cell` classes now include `[Obsolete]` attributes with clear migration guidance to CollectionView + DataTemplate + ReactiveContentView pattern. #### New CollectionView-Compatible Components **ReactiveTextItemView&lt;TViewModel&gt;:** - Vertical layout with primary (16pt) and detail (12pt) labels - Bindable properties: `Text`, `Detail`, `TextColor`, `DetailColor` - Proper padding and opacity styling **ReactiveImageItemView&lt;TViewModel&gt;:** - Horizontal layout with 40x40 image + text stack - Bindable properties: `ImageSource`, `Text`, `Detail`, `TextColor`, `DetailColor` - Proper spacing and alignment ### 🛠️ Technical Implementation - Inherits from `ReactiveContentView&lt;TViewModel&gt;` for full ReactiveUI integration - AOT-compatible with proper `RequiresDynamicCode`/`RequiresUnreferencedCode` attributes - Modern MAUI APIs and cross-platform layout containers - Comprehensive XML documentation - Maintains backward compatibility with .NET 8/9/10 ### 📖 Migration Example ```csharp // Old (now obsolete with [Obsolete] warning) var listView = new ListView { ItemTemplate = new DataTemplate(() => new ReactiveTextCell&lt;MyViewModel&gt;()) }; // New approach using CollectionView var collectionView = new CollectionView { ItemTemplate = new DataTemplate(() => { var itemView = new ReactiveTextItemView&lt;MyViewModel&gt;(); itemView.SetBinding(ReactiveTextItemView&lt;MyViewModel&gt;.TextProperty, nameof(MyViewModel.Title)); itemView.SetBinding(ReactiveTextItemView&lt;MyViewModel&gt;.DetailProperty, nameof(MyViewModel.Subtitle)); return itemView; }) }; ``` **Ready for review** - All requirements completed, compile errors fixed, solution builds successfully. Fixes #4144. <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: glennawatson <[email protected]>
1 parent 6d0812d commit c0dc37c

File tree

8 files changed

+316
-0
lines changed

8 files changed

+316
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,4 @@ src/Tools/
449449
# MSBuild generator editor configs
450450
**/*.GeneratedMSBuildEditorConfig.editorconfig
451451
/app
452+
.dotnet/

src/ReactiveUI.Maui/ReactiveEntryCell.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System;
67
using Microsoft.Maui.Controls;
78

89
namespace ReactiveUI.Maui;
@@ -17,6 +18,7 @@ namespace ReactiveUI.Maui;
1718
[RequiresDynamicCode("ReactiveEntryCell uses methods that require dynamic code generation")]
1819
[RequiresUnreferencedCode("ReactiveEntryCell uses methods that may require unreferenced code")]
1920
#endif
21+
[Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")]
2022
public partial class ReactiveEntryCell<TViewModel> : EntryCell, IViewFor<TViewModel>
2123
where TViewModel : class
2224
{

src/ReactiveUI.Maui/ReactiveImageCell.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System;
67
using Microsoft.Maui.Controls;
78

89
namespace ReactiveUI.Maui;
@@ -17,6 +18,7 @@ namespace ReactiveUI.Maui;
1718
[RequiresDynamicCode("ReactiveImageCell uses methods that require dynamic code generation")]
1819
[RequiresUnreferencedCode("ReactiveImageCell uses methods that may require unreferenced code")]
1920
#endif
21+
[Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")]
2022
public partial class ReactiveImageCell<TViewModel> : ImageCell, IViewFor<TViewModel>
2123
where TViewModel : class
2224
{
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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+
using Microsoft.Maui.Controls;
7+
using Microsoft.Maui.Graphics;
8+
9+
namespace ReactiveUI.Maui;
10+
11+
/// <summary>
12+
/// A <see cref="ReactiveContentView{TViewModel}"/> that displays an image with text content similar to an ImageCell,
13+
/// but designed for use with CollectionView and DataTemplates. This serves as a modern replacement
14+
/// for ReactiveImageCell which relied on the deprecated ListView.
15+
/// </summary>
16+
/// <typeparam name="TViewModel">The type of the view model.</typeparam>
17+
/// <seealso cref="ReactiveContentView{TViewModel}" />
18+
#if NET6_0_OR_GREATER
19+
[RequiresDynamicCode("ReactiveImageItemView uses methods that require dynamic code generation")]
20+
[RequiresUnreferencedCode("ReactiveImageItemView uses methods that may require unreferenced code")]
21+
#endif
22+
public partial class ReactiveImageItemView<TViewModel> : ReactiveContentView<TViewModel>
23+
where TViewModel : class
24+
{
25+
/// <summary>
26+
/// The image source bindable property.
27+
/// </summary>
28+
public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(
29+
nameof(ImageSource),
30+
typeof(ImageSource),
31+
typeof(ReactiveImageItemView<TViewModel>),
32+
default(ImageSource));
33+
34+
/// <summary>
35+
/// The text bindable property for the primary text.
36+
/// </summary>
37+
public static readonly BindableProperty TextProperty = BindableProperty.Create(
38+
nameof(Text),
39+
typeof(string),
40+
typeof(ReactiveImageItemView<TViewModel>),
41+
default(string));
42+
43+
/// <summary>
44+
/// The detail bindable property for the secondary text.
45+
/// </summary>
46+
public static readonly BindableProperty DetailProperty = BindableProperty.Create(
47+
nameof(Detail),
48+
typeof(string),
49+
typeof(ReactiveImageItemView<TViewModel>),
50+
default(string));
51+
52+
/// <summary>
53+
/// The text color bindable property.
54+
/// </summary>
55+
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
56+
nameof(TextColor),
57+
typeof(Color),
58+
typeof(ReactiveImageItemView<TViewModel>),
59+
default(Color));
60+
61+
/// <summary>
62+
/// The detail color bindable property.
63+
/// </summary>
64+
public static readonly BindableProperty DetailColorProperty = BindableProperty.Create(
65+
nameof(DetailColor),
66+
typeof(Color),
67+
typeof(ReactiveImageItemView<TViewModel>),
68+
default(Color));
69+
70+
private readonly Image _image;
71+
private readonly Label _textLabel;
72+
private readonly Label _detailLabel;
73+
74+
/// <summary>
75+
/// Initializes a new instance of the <see cref="ReactiveImageItemView{TViewModel}"/> class.
76+
/// </summary>
77+
public ReactiveImageItemView()
78+
{
79+
_image = new Image
80+
{
81+
WidthRequest = 40,
82+
HeightRequest = 40,
83+
VerticalOptions = LayoutOptions.Center,
84+
HorizontalOptions = LayoutOptions.Start
85+
};
86+
87+
_textLabel = new Label
88+
{
89+
FontSize = 16,
90+
VerticalOptions = LayoutOptions.Center
91+
};
92+
93+
_detailLabel = new Label
94+
{
95+
FontSize = 12,
96+
VerticalOptions = LayoutOptions.Center,
97+
Opacity = 0.7
98+
};
99+
100+
var textStackLayout = new StackLayout
101+
{
102+
Orientation = StackOrientation.Vertical,
103+
VerticalOptions = LayoutOptions.Center,
104+
HorizontalOptions = LayoutOptions.Fill,
105+
Children = { _textLabel, _detailLabel }
106+
};
107+
108+
var mainStackLayout = new StackLayout
109+
{
110+
Orientation = StackOrientation.Horizontal,
111+
VerticalOptions = LayoutOptions.Center,
112+
Padding = 16,
113+
Spacing = 12,
114+
Children = { _image, textStackLayout }
115+
};
116+
117+
Content = mainStackLayout;
118+
119+
// Bind the control properties to the bindable properties
120+
_image.SetBinding(Image.SourceProperty, new Binding(nameof(ImageSource), source: this));
121+
_textLabel.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this));
122+
_textLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(TextColor), source: this));
123+
_detailLabel.SetBinding(Label.TextProperty, new Binding(nameof(Detail), source: this));
124+
_detailLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(DetailColor), source: this));
125+
}
126+
127+
/// <summary>
128+
/// Gets or sets the image source to display.
129+
/// </summary>
130+
public ImageSource? ImageSource
131+
{
132+
get => (ImageSource?)GetValue(ImageSourceProperty);
133+
set => SetValue(ImageSourceProperty, value);
134+
}
135+
136+
/// <summary>
137+
/// Gets or sets the primary text to display.
138+
/// </summary>
139+
public string? Text
140+
{
141+
get => (string?)GetValue(TextProperty);
142+
set => SetValue(TextProperty, value);
143+
}
144+
145+
/// <summary>
146+
/// Gets or sets the detail text to display.
147+
/// </summary>
148+
public string? Detail
149+
{
150+
get => (string?)GetValue(DetailProperty);
151+
set => SetValue(DetailProperty, value);
152+
}
153+
154+
/// <summary>
155+
/// Gets or sets the color of the primary text.
156+
/// </summary>
157+
public Color TextColor
158+
{
159+
get => (Color)GetValue(TextColorProperty);
160+
set => SetValue(TextColorProperty, value);
161+
}
162+
163+
/// <summary>
164+
/// Gets or sets the color of the detail text.
165+
/// </summary>
166+
public Color DetailColor
167+
{
168+
get => (Color)GetValue(DetailColorProperty);
169+
set => SetValue(DetailColorProperty, value);
170+
}
171+
}

src/ReactiveUI.Maui/ReactiveSwitchCell.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System;
67
using Microsoft.Maui.Controls;
78

89
namespace ReactiveUI.Maui;
@@ -17,6 +18,7 @@ namespace ReactiveUI.Maui;
1718
[RequiresDynamicCode("ReactiveSwitchCell uses methods that require dynamic code generation")]
1819
[RequiresUnreferencedCode("ReactiveSwitchCell uses methods that may require unreferenced code")]
1920
#endif
21+
[Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")]
2022
public partial class ReactiveSwitchCell<TViewModel> : SwitchCell, IViewFor<TViewModel>
2123
where TViewModel : class
2224
{

src/ReactiveUI.Maui/ReactiveTextCell.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System;
67
using Microsoft.Maui.Controls;
78

89
namespace ReactiveUI.Maui;
@@ -17,6 +18,7 @@ namespace ReactiveUI.Maui;
1718
[RequiresDynamicCode("ReactiveTextCell uses methods that require dynamic code generation")]
1819
[RequiresUnreferencedCode("ReactiveTextCell uses methods that may require unreferenced code")]
1920
#endif
21+
[Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")]
2022
public partial class ReactiveTextCell<TViewModel> : TextCell, IViewFor<TViewModel>
2123
where TViewModel : class
2224
{
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
using Microsoft.Maui.Controls;
7+
using Microsoft.Maui.Graphics;
8+
9+
namespace ReactiveUI.Maui;
10+
11+
/// <summary>
12+
/// A <see cref="ReactiveContentView{TViewModel}"/> that displays text content similar to a TextCell,
13+
/// but designed for use with CollectionView and DataTemplates. This serves as a modern replacement
14+
/// for ReactiveTextCell which relied on the deprecated ListView.
15+
/// </summary>
16+
/// <typeparam name="TViewModel">The type of the view model.</typeparam>
17+
/// <seealso cref="ReactiveContentView{TViewModel}" />
18+
#if NET6_0_OR_GREATER
19+
[RequiresDynamicCode("ReactiveTextItemView uses methods that require dynamic code generation")]
20+
[RequiresUnreferencedCode("ReactiveTextItemView uses methods that may require unreferenced code")]
21+
#endif
22+
public partial class ReactiveTextItemView<TViewModel> : ReactiveContentView<TViewModel>
23+
where TViewModel : class
24+
{
25+
/// <summary>
26+
/// The text bindable property for the primary text.
27+
/// </summary>
28+
public static readonly BindableProperty TextProperty = BindableProperty.Create(
29+
nameof(Text),
30+
typeof(string),
31+
typeof(ReactiveTextItemView<TViewModel>),
32+
default(string));
33+
34+
/// <summary>
35+
/// The detail bindable property for the secondary text.
36+
/// </summary>
37+
public static readonly BindableProperty DetailProperty = BindableProperty.Create(
38+
nameof(Detail),
39+
typeof(string),
40+
typeof(ReactiveTextItemView<TViewModel>),
41+
default(string));
42+
43+
/// <summary>
44+
/// The text color bindable property.
45+
/// </summary>
46+
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
47+
nameof(TextColor),
48+
typeof(Color),
49+
typeof(ReactiveTextItemView<TViewModel>),
50+
default(Color));
51+
52+
/// <summary>
53+
/// The detail color bindable property.
54+
/// </summary>
55+
public static readonly BindableProperty DetailColorProperty = BindableProperty.Create(
56+
nameof(DetailColor),
57+
typeof(Color),
58+
typeof(ReactiveTextItemView<TViewModel>),
59+
default(Color));
60+
61+
private readonly Label _textLabel;
62+
private readonly Label _detailLabel;
63+
64+
/// <summary>
65+
/// Initializes a new instance of the <see cref="ReactiveTextItemView{TViewModel}"/> class.
66+
/// </summary>
67+
public ReactiveTextItemView()
68+
{
69+
_textLabel = new Label
70+
{
71+
FontSize = 16,
72+
VerticalOptions = LayoutOptions.Center
73+
};
74+
75+
_detailLabel = new Label
76+
{
77+
FontSize = 12,
78+
VerticalOptions = LayoutOptions.Center,
79+
Opacity = 0.7
80+
};
81+
82+
var stackLayout = new StackLayout
83+
{
84+
Orientation = StackOrientation.Vertical,
85+
VerticalOptions = LayoutOptions.Center,
86+
Padding = 16,
87+
Children = { _textLabel, _detailLabel }
88+
};
89+
90+
Content = stackLayout;
91+
92+
// Bind the label properties to the bindable properties
93+
_textLabel.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this));
94+
_textLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(TextColor), source: this));
95+
_detailLabel.SetBinding(Label.TextProperty, new Binding(nameof(Detail), source: this));
96+
_detailLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(DetailColor), source: this));
97+
}
98+
99+
/// <summary>
100+
/// Gets or sets the primary text to display.
101+
/// </summary>
102+
public string? Text
103+
{
104+
get => (string?)GetValue(TextProperty);
105+
set => SetValue(TextProperty, value);
106+
}
107+
108+
/// <summary>
109+
/// Gets or sets the detail text to display.
110+
/// </summary>
111+
public string? Detail
112+
{
113+
get => (string?)GetValue(DetailProperty);
114+
set => SetValue(DetailProperty, value);
115+
}
116+
117+
/// <summary>
118+
/// Gets or sets the color of the primary text.
119+
/// </summary>
120+
public Color TextColor
121+
{
122+
get => (Color)GetValue(TextColorProperty);
123+
set => SetValue(TextColorProperty, value);
124+
}
125+
126+
/// <summary>
127+
/// Gets or sets the color of the detail text.
128+
/// </summary>
129+
public Color DetailColor
130+
{
131+
get => (Color)GetValue(DetailColorProperty);
132+
set => SetValue(DetailColorProperty, value);
133+
}
134+
}

src/ReactiveUI.Maui/ReactiveViewCell.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System;
67
using Microsoft.Maui.Controls;
78

89
namespace ReactiveUI.Maui;
@@ -17,6 +18,7 @@ namespace ReactiveUI.Maui;
1718
[RequiresDynamicCode("ReactiveViewCell uses methods that require dynamic code generation")]
1819
[RequiresUnreferencedCode("ReactiveViewCell uses methods that may require unreferenced code")]
1920
#endif
21+
[Obsolete("ListView and its cells are obsolete in .NET MAUI, please use CollectionView with a DataTemplate and a ReactiveContentView-based view instead. This will be removed in a future release.")]
2022
public partial class ReactiveViewCell<TViewModel> : ViewCell, IViewFor<TViewModel>
2123
where TViewModel : class
2224
{

0 commit comments

Comments
 (0)