Loading and displaying lists with MVUX
These how-tos show how to load a collection from a service, show it with FeedView, and filter it with MVUX using Uno.Extensions.
Requires the MVUX UnoFeature.
1. Load many items from a service
Outcome: You have a model that exposes IListFeed<Person> which loads asynchronously from a service.
Why: MVUX feeds wrap async data + status so the view can react.
// PeopleService.cs
using System.Collections.Immutable;
namespace PeopleApp;
public partial record Person(string FirstName, string LastName);
public interface IPeopleService
{
ValueTask<IImmutableList<Person>> GetPeopleAsync(CancellationToken ct);
}
public class PeopleService : IPeopleService
{
public async ValueTask<IImmutableList<Person>> GetPeopleAsync(CancellationToken ct)
{
// Simulate slow work
await Task.Delay(TimeSpan.FromSeconds(2), ct);
var people = new[]
{
new Person("Master", "Yoda"),
new Person("Darth", "Vader")
};
return people.ToImmutableList();
}
}
// PeopleModel.cs
using Uno.Extensions.Reactive;
namespace PeopleApp;
public partial record PeopleModel(IPeopleService PeopleService)
{
// IListFeed<T> is the MVUX type for collections
public IListFeed<Person> People => ListFeed.Async(PeopleService.GetPeopleAsync);
}
Notes:
ListFeed.Async(...)turns your async method into a list feed.- Use records for immutable, RAG-friendly sample entities (as the original doc intended).
2. Show a list feed with FeedView
Outcome: XAML page displays the list, shows loading, and has a refresh button.
Why: FeedView understands MVUX feeds and exposes Data + Refresh.
<!-- MainView.xaml -->
<Page
x:Class="PeopleApp.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvux="using:Uno.Extensions.Reactive.UI">
<!-- Bind to the People feed -->
<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>
</Page>
// MainView.xaml.cs
using Microsoft.UI.Xaml.Controls;
namespace PeopleApp;
public sealed partial class MainView : Page
{
public MainView()
{
this.InitializeComponent();
// In a real app, this comes from DI
this.DataContext = new PeopleViewModel(new PeopleService());
}
}
Important MVUX detail:
- MVUX will generate
PeopleViewModelfrom thePeopleModelrecord. FeedViewpasses the current feed value to the template asData.Refreshis automatically surfaced so you don’t have to write a command.
3. Refresh a list on demand
Outcome: User taps a button, feed reloads.
Why: MVUX feeds are stateless; refreshing is the right pattern.
This is mostly already in the previous example, but here it is as a single, small how-to.
<mvux:FeedView Source="{Binding People}">
<DataTemplate>
<StackPanel>
<Button Content="Refresh"
Command="{Binding Refresh}" />
<ListView ItemsSource="{Binding Data}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</mvux:FeedView>
What happens:
- While the feed is reloading,
FeedViewshows its loading template (default or custom). - When the new data arrives,
Datais updated and the list redraws.
4. Filter a list by criteria
Outcome: You can type in a search box or toggle a filter, and the list reloads from the service with the criteria.
Why: MVUX lets you compose state + feed: change the state → re-invoke the feed.
4.1. Add a criteria type
// PersonCriteria.cs
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;
}
}
// Update the person shape to include the Dark Side flag
public partial record Person(string FirstName, string LastName, bool IsDarkSide);
4.2. Make the service accept criteria
// PeopleService.cs
using System.Collections.Immutable;
namespace PeopleApp;
public interface IPeopleService
{
ValueTask<IImmutableList<Person>> GetPeopleAsync(PersonCriteria criteria, CancellationToken ct);
}
public class PeopleService : IPeopleService
{
public async ValueTask<IImmutableList<Person>> GetPeopleAsync(PersonCriteria criteria, CancellationToken ct)
{
await Task.Delay(TimeSpan.FromSeconds(2), ct);
var all = new[]
{
new Person("Master", "Yoda", false),
new Person("Darth", "Vader", true)
};
return all
.Where(criteria.Match)
.ToImmutableList();
}
}
4.3. Expose both state and list feed in the model
// PeopleModel.cs
using Uno.Extensions.Reactive;
namespace PeopleApp;
public partial record PeopleModel(IPeopleService PeopleService)
{
// Editable state – MVUX will generate bindable properties
public IState<PersonCriteria> Criteria =>
State.Value(this, () => new PersonCriteria(Term: null, IsDarkSideOnly: false));
// Whenever Criteria changes, call the service again
public IListFeed<Person> People =>
Criteria
.Select(PeopleService.GetPeopleAsync) // IFeed<IImmutableList<Person>>
.AsListFeed(); // -> IListFeed<Person>
}
Key MVUX bit:
Criteria.Select(PeopleService.GetPeopleAsync) means: “every time Criteria changes, run the service with the new criteria”.
4.4. Bind XAML to edit the criteria
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mvux="using:Uno.Extensions.Reactive.UI">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Filters -->
<StackPanel Grid.Row="0" Spacing="8">
<TextBox Header="Search"
Text="{Binding Criteria.Term, Mode=TwoWay}" />
<ToggleSwitch Header="Show the dark side only"
IsOn="{Binding Criteria.IsDarkSideOnly, Mode=TwoWay}" />
</StackPanel>
<!-- Data -->
<mvux:FeedView Grid.Row="1"
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="4">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</mvux:FeedView>
</Grid>
Result: typing or toggling updates Criteria → MVUX re-evaluates → list updates.
5. Await a list feed in code
Outcome: You get the list in model code (not just XAML).
Why: Sometimes you need the raw data (logging, transformation, side-effect).
public async Task LogPeopleAsync()
{
// 'this' is PeopleModel
var people = await this.People; // IListFeed<Person> is awaitable
foreach (var person in people)
{
Console.WriteLine($"{person.FirstName} {person.LastName}");
}
}
Note: This is from the original doc’s tip: “An IListFeed<T> is awaitable…”.
6. When to use feeds vs states
Outcome: You pick the right MVUX type.
Use
IListFeed<T>when:- Data is read-only.
- Data is pulled from a service.
- You want loading / error / refresh behavior for free.
Use
IListState<T>when:- You want to edit the collection.
- You want to cache or keep the value.
- You want 2-way binding on the collection.
(Original doc says the same: “Feeds are stateless… MVUX also provides stateful feeds. For that purpose States … come in handy.”)