-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[Android] Fix RemainingItemsThresholdReachedCommand not firing when CollectionView has Header and Footer both defined #29618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9a1531d
5171b7c
386c526
8211810
8243455
bf72a1e
35aa660
3e2925d
05750a8
75095d8
977d6f0
2f0c688
0611ced
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| using System.Collections.ObjectModel; | ||
| using System.ComponentModel; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Windows.Input; | ||
| using Microsoft.Maui.Controls; | ||
|
|
||
| namespace Microsoft.Maui.Controls.Issues; | ||
|
|
||
| [Issue(IssueTracker.Github, 29588, "CollectionView RemainingItemsThresholdReachedcommand should trigger on scroll near end", PlatformAffected.Android)] | ||
| public class Issue29588 : ContentPage | ||
| { | ||
| public Issue29588() | ||
| { | ||
| BindingContext = new Issue29588ViewModel(); | ||
|
|
||
| var thresholdLabel = new Label | ||
| { | ||
| AutomationId = "29588ThresholdLabel", | ||
| HorizontalOptions = LayoutOptions.Center, | ||
| HeightRequest = 50, | ||
| FontSize = 18 | ||
| }; | ||
| thresholdLabel.SetBinding(Label.TextProperty, nameof(Issue29588ViewModel.ThresholdStatus)); | ||
|
|
||
| var collectionView = new CollectionView | ||
| { | ||
| AutomationId = "29588CollectionView", | ||
| ItemsSource = ((Issue29588ViewModel)BindingContext).Items, | ||
| RemainingItemsThreshold = 1, | ||
| RemainingItemsThresholdReachedCommand = ((Issue29588ViewModel)BindingContext).RemainingItemReachedCommand, | ||
| Header = new Grid | ||
| { | ||
| BackgroundColor = Colors.Bisque, | ||
| Children = | ||
| { | ||
| new Label | ||
| { | ||
| Margin = new Thickness(20,30), | ||
| FontSize = 22, | ||
| Text = "CollectionView does not fire RemainingItemsThresholdReachedCommand when Header and Footer both are set." | ||
| } | ||
| } | ||
| }, | ||
| ItemTemplate = new DataTemplate(() => | ||
| { | ||
| var label = new Label | ||
| { | ||
| Margin = new Thickness(20, 30), | ||
| FontSize = 25 | ||
| }; | ||
| label.SetBinding(Label.TextProperty, "."); | ||
| return label; | ||
| }), | ||
|
|
||
| }; | ||
|
|
||
| var activityIndicator = new ActivityIndicator | ||
| { | ||
| Margin = new Thickness(0, 20) | ||
| }; | ||
| activityIndicator.SetBinding(ActivityIndicator.IsRunningProperty, "IsLoadingMore"); | ||
| activityIndicator.SetBinding(ActivityIndicator.IsVisibleProperty, "IsLoadingMore"); | ||
| collectionView.Footer = activityIndicator; | ||
|
|
||
|
|
||
| var grid = new Grid | ||
| { | ||
| Padding = 20, | ||
| RowDefinitions = | ||
| { | ||
| new RowDefinition { Height = 50 }, // Threshold label | ||
| new RowDefinition { Height = GridLength.Star } // CollectionView | ||
| } | ||
| }; | ||
|
|
||
| grid.Add(thresholdLabel, 0, 0); | ||
| grid.Add(collectionView, 0, 1); | ||
|
|
||
| Content = grid; | ||
| } | ||
| } | ||
|
|
||
| public class Issue29588ViewModel : INotifyPropertyChanged | ||
| { | ||
| private bool _isLoadingMore; | ||
| private int _loadCount = 0; | ||
| private string thresholdStatus; | ||
|
|
||
| public event PropertyChangedEventHandler PropertyChanged; | ||
| public ObservableCollection<string> Items { get; } = new ObservableCollection<string>(); | ||
|
|
||
| public ICommand RemainingItemReachedCommand { get; } | ||
|
|
||
| public bool IsLoadingMore | ||
| { | ||
| get => _isLoadingMore; | ||
| set | ||
| { | ||
| if (_isLoadingMore != value) | ||
| { | ||
| _isLoadingMore = value; | ||
| OnPropertyChanged(); | ||
| } | ||
| } | ||
| } | ||
| public string ThresholdStatus | ||
| { | ||
| get => thresholdStatus; | ||
| set | ||
| { | ||
| if (thresholdStatus != value) | ||
| { | ||
| thresholdStatus = value; | ||
| OnPropertyChanged(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public Issue29588ViewModel() | ||
| { | ||
| ThresholdStatus = "Threshold not reached"; | ||
| RemainingItemReachedCommand = new Command(async () => await LoadMoreItemsAsync()); | ||
| LoadInitialItems(); | ||
| } | ||
|
|
||
| private void LoadInitialItems() | ||
| { | ||
| for (int i = 1; i <= 20; i++) | ||
| { | ||
| Items.Add($"Item {i}"); | ||
| } | ||
| } | ||
|
|
||
| private async Task LoadMoreItemsAsync() | ||
| { | ||
| if (IsLoadingMore) | ||
| return; | ||
|
|
||
| IsLoadingMore = true; | ||
|
|
||
| await Task.Delay(1500); // Simulate API call or long operation | ||
|
|
||
| for (int i = 1; i <= 10; i++) | ||
| { | ||
| Items.Add($"Loaded Item {_loadCount * 10 + i + 20}"); | ||
| } | ||
|
|
||
| _loadCount++; | ||
| IsLoadingMore = false; | ||
| ThresholdStatus = "Threshold reached"; | ||
| } | ||
|
|
||
| protected void OnPropertyChanged([CallerMemberName] string propertyName = null) | ||
| { | ||
| PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||||||||||||||||||||||||||||||||
| using NUnit.Framework; | ||||||||||||||||||||||||||||||||||||
| using UITest.Appium; | ||||||||||||||||||||||||||||||||||||
| using UITest.Core; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| namespace Microsoft.Maui.TestCases.Tests.Tests.Issues; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| internal class Issue29588 : _IssuesUITest | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| public override string Issue => "CollectionView RemainingItemsThresholdReachedcommand should trigger on scroll near end"; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| public override string Issue => "CollectionView RemainingItemsThresholdReachedcommand should trigger on scroll near end"; | |
| public override string Issue => "CollectionView RemainingItemsThresholdReached command should trigger on scroll near end"; |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition: The RemainingItemReachedCommand is async and includes a 1.5 second delay (Task.Delay(1500) in LoadMoreItemsAsync()), but the test immediately checks the label text after scrolling. The assertion on line 25 might execute before ThresholdStatus is updated to "Threshold reached". Consider adding a wait/polling mechanism after the scrolling to ensure the async command completes, or use a framework method that waits for the text to change to the expected value.
| Assert.That(App.FindElement("29588ThresholdLabel").GetText(), Is.EqualTo("Threshold reached")); | |
| // Wait up to 5 seconds for the label text to become "Threshold reached" | |
| const int timeoutMs = 5000; | |
| const int pollIntervalMs = 200; | |
| var start = DateTime.UtcNow; | |
| string labelText = ""; | |
| do | |
| { | |
| labelText = App.FindElement("29588ThresholdLabel").GetText(); | |
| if (labelText == "Threshold reached") | |
| break; | |
| System.Threading.Thread.Sleep(pollIntervalMs); | |
| } | |
| while ((DateTime.UtcNow - start).TotalMilliseconds < timeoutMs); | |
| Assert.That(labelText, Is.EqualTo("Threshold reached")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in the Issue description: "RemainingItemsThresholdReachedcommand" should have a space before "command".