Search code examples
c#web-servicesmauicollectionview

Why is my CollectionView adding duplicate items in .NET MAUI?


Why are the same items added to my CollectionView repeatedly?

Here's what happens exactly:
On every load (on scroll) I want to load 5 more items into my CollectionView, but this is what happens:

  • The items 1,2,3,4,5 are added, then on scroll 1,2,3,4,5 are added again, and 1,2,3,4,5 again on scroll, etc.
  • Using Task.Delay(1000) the items 1,2,3,4,5 are added, then 1,2,3,4,5 again before adding 6,7,8,9,10 etc.
  • Using DisplayAlert() items are added without repitition for some reason. Obviously it's not a solution.

It looks like the delay that the display alert causes, gives the script time to process the next items. Obviously I do not want to display an alert but the collection behaves as expected when I use it.

This is my CollectionView with "posts":

<CollectionView ItemsSource="{Binding Posts}"
                ItemTemplate="{StaticResource CommunityItemTemplateSelector}"
                RemainingItemsThresholdReachedCommand="{Binding PopulateCommand}"
                RemainingItemsThreshold="0">
    <!-- Using DataTemplates to populate -->
</CollectionView>

This is the ViewModel populating it:
Populates the collection of items with new "posts" when the page appears or you hit the bottom of the CollectionView.

private CommunityService communityService { get; set; }

private int start = 0;
private int limit = 5;
private List<CommunityItem> posts { get; set; } = new();
public ObservableRangeCollection<CommunityItem> Posts { get; private set; } = new();

public Command OnAppearingCommand { get; private set; }
public Command PopulateCommand { get; private set; }

public CommunityViewModel(CommunityService communityService)
{
    this.communityService = communityService;

    OnAppearingCommand = new Command(Appearing);
    PopulateCommand = new Command(Populate);
}

private void Appearing()
{
    Posts.Clear();
    Populate();
}

private async void Populate()
{
    posts.Clear();

    await Task.Delay(250);

    posts = await communityService.GetPosts(start, limit);

    start += limit;

    if (posts.Count > 0)
        Posts.AddRange(posts);
}

This is the API getting new "posts":
It's not the API, this has been tested with Postman and works fine!

public class CommunityService : BaseService
{
    public List<CommunityItem> List = new();

    public async Task<List<CommunityItem>> GetPosts(int start, int limit)
    {
        string range = "&start=" + start + "&limit=" + limit;

        try
        {
            var response = await http.GetAsync(this.uri + "/post" + range + this.key);

            if (response.IsSuccessStatusCode && response.Content is not null)
                List = await response.Content.ReadFromJsonAsync<List<CommunityItem>>();

            // Somewhat solving the problem:
            await Task.Delay(1000);

            // Solving it completely for some reason, but shows an alert:
            await Application.Current.MainPage.DisplayAlert("Debug", "Waiting", "OK");
        }
        catch (Exception e)
        {
            // Debug
        }

        return List;
    }
}

Solution

  • Well, according to Panagiotis Kanavos's comment, as a wiki answer:

    it's because of async void, which is a bug in itself. There's no way to await async void calls so it's possible the command "completes" immediately and the component keeps asking for new data repeatedly, before communityService.GetPosts has a chance to complete and update the counts. Either make your code synchronous or use the AsyncRelayCommand from the MAUI Community Toolkit.

    Switching to AsyncRelayCommand can solve the problem to a certain extent.