Xamarin.X247Grad.Collections.Consuming 1.0.0

dotnet add package Xamarin.X247Grad.Collections.Consuming --version 1.0.0
NuGet\Install-Package Xamarin.X247Grad.Collections.Consuming -Version 1.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Xamarin.X247Grad.Collections.Consuming" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Xamarin.X247Grad.Collections.Consuming --version 1.0.0
#r "nuget: Xamarin.X247Grad.Collections.Consuming, 1.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Xamarin.X247Grad.Collections.Consuming as a Cake Addin
#addin nuget:?package=Xamarin.X247Grad.Collections.Consuming&version=1.0.0

// Install Xamarin.X247Grad.Collections.Consuming as a Cake Tool
#tool nuget:?package=Xamarin.X247Grad.Collections.Consuming&version=1.0.0

Installation

Include the package in your Portable Code.

Install-Package Xamarin.X247Grad.Collections.Consuming -Version 1.0.0

Principle

When filling a list view with items that come from an asynchroneous endpoint, it is often desireable to automatically keep up with the seen items and request more when the list is scrolled to a certain point. For this, an AsyncCollector is provided, extending a standard ObservableCollection to provide a way to consume an IAsyncEnumerable. This enumerable can then take care of paging and request cancellation. In addition, ContinuousRequestsBehavior captures a list's item appearing events and translates them to requests to that collection.

Usage

First, an appropriate generator is needed. This can easily be done by writing an async method returning an IAsyncEnumerable.

private static async IAsyncEnumerable<string> GenerateData([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    // Iterate all pages.
    foreach (var page in Enumerable.Range(0, 10000))
    {
        // For each page, return their items.
        foreach (var item in Enumerable.Range(0, 20))
            yield return $"Item {item}, page {page}";

        // Simulate loading the next page.
        await Task.Delay(3000, cancellationToken);
    }
}

This method provides a batch of items and then loads the next items. To connect it to a list view, it will be passed within an AsyncCollector.

List.ItemsSource = new AsyncCollector<string>(GenerateData());

Within the XAML file of the page, the behavior needs to be attached. First, import the necessary namespace.

<ContentPage
  xmlns:behaviors="clr-namespace:Xamarin.X247Grad.Collections.Consuming.Behaviors;assembly=Xamarin.X247Grad.Collections.Consuming">
</ContentPage>

The behavior is then attached to the list that it captures the appearing item position of.

<ListView>
  <ListView.Behaviors>
      <behaviors:ContinuousRequestsBehavior />
  </ListView.Behaviors>
  <ListView.ItemTemplate>...</ListView.ItemTemplate>
</ListView>

With this behavior attached, appearing items will be send as requests to the list's source, if it is currently assigned to satisfy requests.

Working indicator

The AsyncCollector tracks when it is running and publishes that state with the Working event. This can be used to indicate loading while the underlying enumerator produces more items. A connection can for example be established like so.

// Create source with "Working" handler.
  var source = new AsyncCollector<string>(GenerateData());
  source.Working += (o, working) => Status.IsRunning = working;

  // Assign item source.
  List.ItemsSource = source;

For the full example see ConsumeApi.xaml and ConsumeApi.xaml.cs.

Replacing a source, disposal

After the collection is no longer needed (the page is unloading or the source is replaced), it should be disposed of properly. A simple way to deal with replacing a source is to track the last assigned source and disposing of it.

private void UpdateCaption(object sender, TextChangedEventArgs e)
{
    // Stop and dispose the old source, preventing it from keeping it's resources open.
    _lastSource?.Dispose();

    // Create new source with "Working" handler.
    var source = new AsyncCollector<string>(GenerateData(e.NewTextValue));
    source.Working += (o, working) => Status.IsRunning = working;

    // Assign item source.
    List.ItemsSource = _lastSource = source;
}

Depending on the lifecycle of your page, a disposal call should also be placed in the Disappearing handler to prevent the underlying enumerable to keep pending.

For an example of dealing with updated sources see UpdateRequest.xaml and UpdateRequest.xaml.cs.

Synchronization context

Modification of the actual collection, as well as indication of the working status, needs to happen on the main thread. The constructor will capture the synchronization context at the time it is invoked. When the collector is created outside of the main thread, the synchronization context of the main thread should be captured beforehand and then passed as the constructor's second argument. Usually, construction outside of the main thread will not be necessary.

Further notes

When using a view template, the list views CachingStrategy might need to be set to RecycleElementAndDataTemplate.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0 449 10/21/2020