How to create a list feed
In this tutorial, you will learn how to create an MVUX project that asynchronously requests and displays a collection of items from a service, and enables refreshing the data.
In this tutorial, you will learn how to create an MVUX project and the basic usage of a list-feed (IListFeed<T>), and the FeedView control.
- For our data, we're going to create a service that asynchronously provides a collection of
Personentities upon request. - You'll learn how to use a feed to asynchronously request this data from the service.
- How to display the data on the UI
- How to use the
FeedViewcontrol to display the data and automatically respond to the current feed status. - Use a refresh button to retrieve the latest weather data on-demand.
PeopleApp Sample
You can find the code of this tutorial here.
Create the Model
Create an MVUX project by following the steps in this tutorial, and name your project PeopleApp.
Add a class named PeopleService.cs, and replace its content with the following:
namespace PeopleApp; public partial record Person(string FirstName, string LastName); public interface IPeopleService { ValueTask<IImmutableList<Person>> GetPeopleAsync(CancellationToken ct); } public class PeopleService { public async ValueTask<IImmutableList<Person>> GetPeopleAsync(CancellationToken ct) { await Task.Delay(TimeSpan.FromSeconds(2), ct); var people = new Person[] { new Person(FirstName: "Master", LastName: "Yoda"), new Person(FirstName: "Darth", LastName: "Vader") }; return people.ToImmutableList(); } }We're using a record for the
Persontype on purpose, as records are designed to be immutable to ensure the purity of objects as well as other features.The
IListFeed<T>is a feed type tailored for dealing with collections.Create a class named PeopleModel.cs replacing its content with the following:
using Uno.Extensions.Reactive; namespace PeopleApp; public partial record PeopleModel(IPeopleService PeopleService) { public IListFeed<Person> People => ListFeed.Async(PeopleService.GetPeopleAsync); }Note
Feeds (
IFeed<T>andIListFeed<T>for collections) are used as a gateway to asynchronously request data from a service and wrap the result or an error if any in metadata to be displayed in the View in accordingly. Learn more about list-feeds here.Tip
Feeds are stateless and are there for when the data from the service is read-only and we're not planning to enable edits to it. MVUX also provides stateful feeds. For that purpose States (
IState<T>and<IListState<T>for collections) come in handy. Refer to this tutorial to learn more about states.
Data-bind the view
PeopleModel exposes a People property which is an IListFeed<T> where T is a Person.
This is similar in concept to an IObservable<IEnumerable<T>>, where an IListFeed<T> represents a sequence of person-collections obtained from the service.
Tip
An IListFeed<T> is awaitable, meaning that to get the value of the feed you would do the following in the model:
IImmutableList<Person> people = await this.People;
To make it possible to data bind to feeds, the MVUX analyzers read the PeopleModel
and generate a ViewModel called PeopleViewModel,
which exposes properties that the View can data bind to.
Open the file
MainView.xamland add the following namespace to the XAML:xmlns:mvux="using:Uno.Extensions.Reactive.UI"Replace anything inside the
Pagecontents with the following code:<mvux:FeedView Source="{Binding People}"> <DataTemplate> <ListView ItemsSource="{Binding Data}"> <ListView.Header> <Button Content="Refresh" Command="{Binding Refresh}" /> </ListView.Header> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Spacing="5"> <TextBlock Text="{Binding FirstName}"/> <TextBlock Text="{Binding LastName}"/> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </DataTemplate> </mvux:FeedView>Tip
The
FeedViewwraps its source (in this case thePeoplefeed) in aFeedViewStateobject, and makes the current value of the feed accessible via itsDataproperty as well as theRefreshproperty, which is a command that explicitly triggers reloading the data.Press F7 to navigate to open code-view, and in the constructor, after the line that calls
InitializeComponent(), add the following line:this.DataContext = new PeopleViewModel(new PeopleService());Click F5 to run the project.
When the app loads you'll notice how the
ProgressTemplateshows (if you've included one), till the data is received from the service (2 seconds).
Once the data is available, the
FeedViewswitches to itsValueTemplate(the first defaultDataTemplatein our example), and displays the people list.
If you're using Visual-Studio 2022, Right-click the
PeopleAppproject, and navigate to Dependencies. Open up netX.0-windows10.0... → Analyzers. Under Uno.Extensions.Reactive.Generator, expand Uno.Extensions.Reactive.FeedGenerator. Here you'll be able to inspect all files MVUX has generated for you, and learn more about how MVUX runs behind the scenes.
Read inspecting the generated code for more.
Add search / filtering criteria
Let assume that we want to display only items that match some given criteria.
Update the file PeopleService.cs, to add a
PersonCriteriaparameter to theGetPeopleAsyncmethod:namespace PeopleApp; public partial record PersonCriteria(string? Term, bool IsDarkSideOnly) { public bool Match(Person person) { if (Term is { Length: > 0 } term && !person.FirstName.Contains(term,StringComparison.OrdinalIgnoreCase) && !person.LastName.Contains(term, StringComparison.OrdinalIgnoreCase)) { return false; } if (IsDarkSideOnly && !person.IsDarkSide) { return false; } return true; } } public partial record Person(string FirstName, string LastName, bool IsDarkSide); public interface IPeopleService { ValueTask<IImmutableList<Person>> GetPeopleAsync(PersonCriteria criteria, CancellationToken ct); } public class PeopleService { public async ValueTask<IImmutableList<Person>> GetPeopleAsync(PersonCriteria criteria, CancellationToken ct) { await Task.Delay(TimeSpan.FromSeconds(2), ct); var people = new Person[] { new Person(FirstName: "Master", LastName: "Yoda", IsDarkSide: false), new Person(FirstName: "Darth", LastName: "Vader", IsDarkSide: true) }; return people.Where(criteria.Match).ToImmutableList(); } }Update the PeopleModel.cs file with the following:
using Uno.Extensions.Reactive; namespace PeopleApp; public partial record PeopleModel(IPeopleService PeopleService) { public IState<PersonCriteria> Criteria => State.Value(this, () => new PersonCriteria()); public IListFeed<Person> People => Criteria.Select(PeopleService.GetPeopleAsync).AsListFeed(); }Note
Here we use the
AsListFeedoperator to create theListFeed. This converts aFeed<ImmutableList<T>>to anIListFeed<T>(Cf. AsListFeed).Finally update your view
MainView.xamlto add UI to edit the criteria:<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel> <TextBox Header="Search term" Text="{Binding Criteria.Term, Mode=TwoWay}" /> <ToggleSwitch Header="Show the dark side only" IsOn="{Binding Criteria.IsDarkSideOnly, Mode=TwoWay}" /> </StackPanel> <mvux:FeedView Source="{Binding People}"> <DataTemplate> <ListView ItemsSource="{Binding Data}"> <ListView.Header> <Button Content="Refresh" Command="{Binding Refresh}" /> </ListView.Header> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Spacing="5"> <TextBlock Text="{Binding FirstName}"/> <TextBlock Text="{Binding LastName}"/> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </DataTemplate> </mvux:FeedView> </Grid>