Usage in applications of feeds

The recommended use for feeds is only in models.

The Reactive framework allows you to design state-less models, focusing on the presentation logic. They are expected to expose some IFeed, IListFeed, IState or IListState. A bindable friendly and higly performant view model is then automatically generated by the Reactive framework. It is this class that will hold the state and which has to be set as DataContext of your page.

Note

The ViewModel of a model is created when the class name matches the regex "Model$". You can customize that behavior using the [ImplicitBindables("MyApp\.Presentation\.\.*Model$")] on the assembly or the [ReactiveBindable] attribute on you model itself.

Note

To ease models declaration, public properties are also accessible on the view model class. You can also access the model itself through the Model property.

Display some data in the UI (Model to View)

To render a feed in the UI, you only have to expose it through a public property:

public IFeed<Product[]> Products = Feed.Async(_productService.GetProducts);

Then in your page, you can add a FeedView:

<Page x:Class="MyProject.MyPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:uer="using:Uno.Extensions.Reactive.UI">

    <uer:FeedView Source="{Binding Products}">
        <DataTemplate>
            <ListView ItemsSource="{Binding Data}" />
        </DataTemplate>
    </uer:FeedView>

Refreshing a data

The FeedView exposes a Refresh command directly in the data context of its content. You can use this command to trigger a refresh from the view, like a "pull to refresh".

<Page x:Class="MyProject.MyPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:uer="using:Uno.Extensions.Reactive.UI">

    <uer:FeedView Source="{Binding Products}">
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition />
                <Grid.RowDefinitions>

                <Button Grid.Row="0" 
                        Content="Refresh" 
                        Command="{Binding Refresh}" />
                <ListView Grid.Row="1" 
                          ItemsSource="{Binding Data}" />
            </Grid>
        </DataTemplate>
    </uer:FeedView>

Refreshing using a RefreshContainer (a.k.a. pull to refresh)

The RefreshContainer does not have a Command property to which you can data-bind the command exposed by the FeedView. This is because the RefreshContainer needs to know when the refresh has completed in order to remove the loading wheel.

However, Uno.Extensions defines the IAsyncCommand.IsExecuting property which can be used to track the completion of the refresh. Uno.Extensions also defines a RefreshContainerExtensions.Command attached property which allow to directly data-bind the refresh command (or any generated feed command) to the RefreshContainer.

<Page x:Class="MyProject.MyPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:uer="using:Uno.Extensions.Reactive.UI">

    <uer:FeedView Source="{Binding Products}" 
                  RefreshingState="None">
        <DataTemplate>
            <RefreshContainer uer:RefreshContainerExtensions.Command="{Binding Refresh}">
                <ListView ItemsSource="{Binding Data}" />
            </RefreshContainer>
        </DataTemplate>
    </uer:FeedView>
Tip

When you use a RefreshContainer to trigger the refresh of your feed, as the RefreshContainer do have its own loading indicator, you might have 2 loading indicators, one from the RefreshContainer itself, and a second one from the FeedView. In order to avoid that, you should instruct your FeedView to not use the loading state in case of refresh by setting RefreshingState="None".

Pagination

When your source supports pagination (like ListFeed.Paginated), you can data-bind the collection exposed by the generated view model to a ListView to automatically enable pagination.

<uer:FeedView Source="{Binding Items}">
    <DataTemplate>
        <ListView ItemsSource="{Binding Data}" />
    </DataTemplate>
</uer:FeedView>

The collection exposed for binding by the generated view models also exposes some extended properties, so you can enhance the UX of the pagination, like display a loading indicator while loading the next page.

<uer:FeedView Source="{Binding Items}">
    <DataTemplate>
        <ListView Grid.Row="1"
                  ItemsSource="{Binding Data}">
            <ListView.Footer>
                <ProgressBar Visibility="{Binding Data.ExtendedProperties.IsLoadingMoreItems}"
                             IsIndeterminate="{Binding Data.ExtendedProperties.IsLoadingMoreItems}"
                             HorizontalAlignment="Stretch" />
            </ListView.Footer>
        </ListView>
    </DataTemplate>
</uer:FeedView>

Selection

The reactive framework has embedded support of selected item of a ListFeed data-bound to a Selector, like the ListView. It is not required to data-bind (and synchronize) the ListView.SelectedItem, it will instead be automatically pushed from and to the view.

<uer:FeedView Source="{Binding Items}">
    <DataTemplate>
        <ListView ItemsSource="{Binding Data}" />
    </DataTemplate>
</uer:FeedView>

And in the model:

public IListState<int> Items => ListView
    .Value(this, () => ImmutableList.Create(1, 2, 3, 4, 5))
    .Selection(SelectedItem);

public IState<int> SelectedItem => State.Value(this, () => 3);

Project selected item into another entity

It is possible to synchronize the selected item key into another aggregate root object, for instance, assuming Profile and Country records:

public record Profile(string? FirstName, string? CountryId);

public record Country(string Id, string Name);

The selected item automatically stored into the Profile.CountyId by doing:

public IState<Profile> Profile => State.Async(this, _svc.GetProfile);

public IListState<Country> Countries => ListState
    .Async(this, _svc.GetCountries)
    .Selection(Profile, p => p.CountryId);
<uer:FeedView Source="{Binding Countries}">
    <DataTemplate>
        <ComboBox ItemsSource="{Binding Data}" />
    </DataTemplate>
</uer:FeedView>
Note

As the IState<Porfile> Profile state might be None when an item is being selected, the Profile class must have either a parameter-less contructor, either a constructor that accepts only nullable parameters.

Commands

The generated view model of a model will automatically re-expose as ICommand public methods that are compatible (cf. "general rules" below).

For instance, in your model:

public async ValueTask Share() 
{

}

This will be exposed into an ICommand that can be data-bound to the Command property of a Button

<Button Command="{Binding Share}" 
        Content="Share" />

By default, if the method has a parameter T myValue and there is a property Feed<T> MyValue on the same class (matching type and name), that parameter will automatically be filled from that feed.

For instance, in your model:

public IFeed<string> Message { get; }

public async ValueTask Share(string message) 
{

}

Can be used with or without any CommandParameter from the view:

<!-- Without CommandParameter -->
<!-- 'message' arg in the 'Share' method will be the current value of the Message _feed_ -->
<Button Command="{Binding Share}" 
        Content="Share" />

<!-- With CommandParameter -->
<!-- 'message' arg in the 'Share' method will be "hello world" -->
<Button Command="{Binding Share}" 
        CommandParameter="hello world" 
        Content="Share" />

You can also use both "feed parameters" and "view parameter" (i.e. the value of the CommandParameter):

public IFeed<MyEntity> Entity { get; }

public async ValueTask Share(MyEntity entity, string origin) 
{
}
<Button Command="{Binding Share}" 
        CommandParameter="command_bar" 
        Content="Share" />

General rules for methods to be re-exposed as commands are:

  • At most one paramater that cannot be resolved from a Feed property in your VM (a.k.a the CommandParameter);
  • At most one CancellationToken;
  • Method can be sync, or async
Note

The automatic parameters resolution be configured using attributes:

  • [ImplicitFeedCommandParameters(is_enabled)] on assembly or class to enable or disable the implicit parameters resolution.
  • [FeedParameter("MyEntity")] to explicit the property to use for a given parameter, e.g.
    public IFeed<string> Message { get; }
    
    public async ValueTask Share([FeedParameter(nameof(Message))] string msg)
    {
    }