diff --git a/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs index f2b7565c4ef4..596f1224c28a 100644 --- a/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs +++ b/src/Controls/src/Core/Handlers/Items/Android/ItemsSources/ObservableGroupedSource.cs @@ -226,10 +226,13 @@ void UpdateGroupTracking() for (int n = 0; n < _groupSource.Count; n++) { - var source = ItemsSourceFactory.Create(_groupSource[n] as IEnumerable, _groupableItemsView, this); - source.HasFooter = _hasGroupFooters; - source.HasHeader = _hasGroupHeaders; - _groups.Add(source); + if (_groupSource[n] is IEnumerable list) + { + var source = ItemsSourceFactory.Create(list, _groupableItemsView, this); + source.HasFooter = _hasGroupFooters; + source.HasHeader = _hasGroupHeaders; + _groups.Add(source); + } } } diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28827.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue28827.xaml new file mode 100644 index 000000000000..0f8a263f8510 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28827.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28827.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue28827.xaml.cs new file mode 100644 index 000000000000..197efee92a37 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28827.xaml.cs @@ -0,0 +1,216 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 28827, "[Android] Group Header/Footer set for all Items when IsGrouped is True for ObservableCollection", PlatformAffected.Android)] +public partial class Issue28827 : ContentPage +{ + Issue28827CollectionViewViewModel _viewModel; + public Issue28827() + { + InitializeComponent(); + BindingContext = _viewModel = new Issue28827CollectionViewViewModel(); + } + + void OnGroupHeaderTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (GroupHeaderTemplateNone.IsChecked) + { + _viewModel.GroupHeaderTemplate = null; + } + else if (GroupHeaderTemplateGrid.IsChecked) + { + _viewModel.GroupHeaderTemplate = new DataTemplate(() => + { + return new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10), + Children = + { + new Label + { + Text = "Group Header Template (Grid View)", + FontSize = 18, + AutomationId = "GroupHeaderTemplate", + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Green + } + } + }; + }); + } + } + + void OnGroupFooterTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (GroupFooterTemplateNone.IsChecked) + { + _viewModel.GroupFooterTemplate = null; + } + else if (GroupFooterTemplateGrid.IsChecked) + { + _viewModel.GroupFooterTemplate = new DataTemplate(() => + { + return new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10), + Children = + { + new Label + { + Text = "Group Footer Template (Grid View)", + FontSize = 18, + AutomationId = "GroupFooterTemplate", + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Red + } + } + }; + }); + } + } + + void OnIsGroupedChanged(object sender, CheckedChangedEventArgs e) + { + if (IsGroupedFalse.IsChecked) + { + _viewModel.IsGrouped = false; + } + else if (IsGroupedTrue.IsChecked) + { + _viewModel.IsGrouped = true; + } + } +} + +public class Issue28827CollectionViewViewModel : INotifyPropertyChanged +{ + DataTemplate _groupHeaderTemplate; + DataTemplate _groupFooterTemplate; + DataTemplate _itemTemplate; + bool _isGrouped = false; + ObservableCollection _observableCollection; + + public event PropertyChangedEventHandler PropertyChanged; + + public Issue28827CollectionViewViewModel() + { + LoadItems(); + ItemTemplate = new DataTemplate(() => + { + var stackLayout = new StackLayout + { + Padding = new Thickness(10), + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center + }; + + var label = new Label + { + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Center + }; + label.SetBinding(Label.TextProperty, "Caption"); + stackLayout.Children.Add(label); + return stackLayout; + }); + + GroupHeaderTemplate = new DataTemplate(() => + { + var stackLayout = new StackLayout + { + BackgroundColor = Colors.LightGray + }; + var label = new Label + { + FontAttributes = FontAttributes.Bold, + FontSize = 18 + }; + label.SetBinding(Label.TextProperty, "Key"); + stackLayout.Children.Add(label); + return stackLayout; + }); + } + + void LoadItems() + { + _observableCollection = new ObservableCollection + { + new Issue28827ItemModel { Caption = "Item 1" }, + new Issue28827ItemModel { Caption = "Item 2" }, + new Issue28827ItemModel { Caption = "Item 3" } + }; + } + + public DataTemplate GroupHeaderTemplate + { + get => _groupHeaderTemplate; + set { _groupHeaderTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate GroupFooterTemplate + { + get => _groupFooterTemplate; + set { _groupFooterTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate ItemTemplate + { + get => _itemTemplate; + set { _itemTemplate = value; OnPropertyChanged(); } + } + + public bool IsGrouped + { + get => _isGrouped; + set { _isGrouped = value; OnPropertyChanged(); } + } + + public object ItemsSource + { + get => _observableCollection; + } + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + if (propertyName == nameof(IsGrouped)) + { + OnPropertyChanged(nameof(ItemsSource)); + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} + +internal class Issue28827Grouping : List +{ + public TKey Key { get; } + + public Issue28827Grouping(TKey key, List items) : base(items) + { + Key = key; + } + + public override string ToString() + { + return Key?.ToString() ?? base.ToString(); + } +} + +internal class Issue28827ItemModel +{ + public string Caption { get; set; } + + public override string ToString() + { + return !string.IsNullOrEmpty(Caption) ? Caption : base.ToString(); + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28827.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28827.cs new file mode 100644 index 000000000000..aa461914f578 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28827.cs @@ -0,0 +1,28 @@ +#if TEST_FAILS_ON_WINDOWS // NullReferenceException occurs when switching isGrouped to true +// refer to https://github.com/dotnet/maui/issues/28824 +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue28827 : _IssuesUITest +{ + public override string Issue => "[Android] Group Header/Footer set for all Items when IsGrouped is True for ObservableCollection"; + public Issue28827(TestDevice device) + : base(device) + { } + + [Test] + [Category(UITestCategories.CollectionView)] + public void CVGroupHFTemplateWithObservableCollection() + { + App.WaitForElement("collectionView"); + App.Tap("IsGroupedTrue"); + App.Tap("GroupHeaderTemplateGrid"); + App.Tap("GroupFooterTemplateGrid"); + App.WaitForNoElement("GroupHeaderTemplate"); + App.WaitForNoElement("GroupFooterTemplate"); + } +} +#endif \ No newline at end of file