What are feeds?

Feeds are there to manage asynchronous operations (for example requesting data from a service) and expose the result to the View in an efficient manner.

They provide out of the box support for task-based methods as well as Async-Enumerables.

Feeds include additional metadata that indicates whether the operation is still in progress, ended in an error, or whether the data that was returned contains any entries or was empty.

Feeds are stateless

Feeds are typically used to request data from services and expose it in a stateless manner so that the resulting data can be displayed by the View.

Feeds are stateless and do not provide support for reacting to changes the user makes to the data on the View. The data can only be reloaded and refreshed upon request which is when the underlying task or IAsyncEnumerable will be invoked and the data refreshed. In other words, a feed is a read-only representation of the data received from the server.

In contrast to feeds, states (IState or IListState), as the name suggests, are stateful and keep track of the latest value, as updates are applied.

Sources: How to create a feed

Async

Feeds can be created from an async method using the Feed.Async factory method.

public static IFeed<T> Async<T>(Func<CancellationToken, Task<T>> asyncFunc);

Here’s an example:

private IWeatherService _weatherService;
public IFeed<WeatherInfo> Weather => Feed.Async(async ct => await _weatherService.GetCurrentWeather(ct));

AsyncEnumerable

This adapts an IAsyncEnumerable<T> into a feed using Feed.AsyncEnumerable.

public static IFeed<T> AsyncEnumerable<T>(Func<CancellationToken, IAsyncEnumerable<T>> asyncEnumerableFunc);

Here's an example:

private IWeatherService _weatherService;

public IFeed<WeatherInfo> Weather => Feed.AsyncEnumerable(() => GetWeather());

private async IAsyncEnumerable<WeatherInfo> GetWeather([EnumeratorCancellation] CancellationToken ct = default)
{
    while (!ct.IsCancellationRequested)
    {
        yield return await _weatherService.GetCurrentWeather(ct);
        await Task.Delay(TimeSpan.FromHours(1), ct);
    }
}
Note

Make sure to use a CancellationToken marked with the [EnumeratorCancellation] attribute. This token will be flagged as cancelled when the last subscription of the feed is being removed. Typically this will be when the ViewModel is being disposed.

Create

This gives you the ability to create a specialized feed by dealing directly with messages using Feed.Create.

Note

This is designed for advanced usage and should probably not be used directly in apps.

public static IFeed<T> Create<T>(Func<CancellationToken, IAsyncEnumerable<Message<T>>> messageFunc);

Here's an example:

public IFeed<WeatherInfo> Weather => Feed.Create(GetWeather);

private async IAsyncEnumerable<Message<WeatherInfo>> GetWeather([EnumeratorCancellation] CancellationToken ct = default)
{
    var message = Message<WeatherInfo>.Initial;
    var weather = Option<WeatherInfo>.Undefined();
    var error = default(Exception);
    while (!ct.IsCancellationRequested)
    {
        try
        {
            weather = await _weatherService.GetCurrentWeather(ct);
            error = default;
        }
        catch (Exception ex)
        {
            error = ex;
        }

        yield return message = message.With().Data(weather).Error(error);
        await Task.Delay(TimeSpan.FromHours(1), ct);
    }
}

Operators: How to interact with a feed

You can apply some operators directly on any feed.

Tip

You can use the linq syntax with feeds:

private IFeed<int> _values;

public IFeed<string> Value => from value in _values
  where value == 42
  select value.ToString();

Where

Applies a predicate on the data.

Be aware that unlike IEnumerable, IObservable, and IAsyncEnumerable, if the predicate returns false, a message with a None data will be published.

public static IFeed<T> Where<T>(this IFeed<T> feed, Func<T, bool> predicate);

Select

Synchronously projects each data from the source feed.

public static IFeed<TResult> Select<TSource, TResult>(this IFeed<TSource> feed, Func<TSource, TResult> selector);

Here's an exmaple:

public IFeed<WeatherAlert> Alert => Weather
    .Where(weather => weather.Alert is not null)
    .Select(weather => weather.Alert!);

SelectAsync

Asynchronously projects each data from the source feed.

public static IFeed<TResult> SelectAsync<TSource, TResult>(this IFeed<TSource> feed, Func<TSource, CancellationToken, Task<TResult>> selector);

Here's an example:

public IFeed<string> AlertDetails => Alert
    .SelectAsync(async (alert, ct) => await _weatherService.GetAlertDetails(alert, ct));