Building a Real-Time Search

Problem

Displaying live search results as the user types is a common feature in modern applications. This requires updating the search results every time the search term changes, either per key-press or on submission. Without a proper mechanism, managing this can be complex and inefficient.

Solution

Uno.Extensions.MVUX provides a seamless way to live update search results using IState<string> and SelectAsync on the IState to react to state changes. This allows for a dynamic and responsive search experience.

Using MVUX to Create a Reactive Search Experience

1. SearchModel.cs

public partial class SearchModel
{
    private readonly INavigator _navigator;
    private readonly IRecipeService _recipeService;
    private readonly IMessenger _messenger;

    public SearchModel(SearchFilter? filter, INavigator navigator, IRecipeService recipeService, IMessenger messenger)
    {
        _navigator = navigator;
        _recipeService = recipeService;
        _messenger = messenger;

        Filter = State.Value(this, () => filter ?? new SearchFilter())
            .Observe(_messenger, f => f);
    }

    public IState<string> Term => State<string>.Value(this, () => string.Empty)
        .Observe(_messenger, t => t);

    public IState<SearchFilter> Filter { get; }

    public IListState<Recipe> Results => ListState.FromFeed(this, Feed
        .Combine(Term, Filter)
        .SelectAsync(Search)
        .AsListFeed())
        .Observe(_messenger, r => r.Id);

    ...

    private async ValueTask<IImmutableList<Recipe>> Search((string term, SearchFilter filter) inputs, CancellationToken ct)
    {
        var searchedRecipes = await _recipeService.Search(inputs.term, inputs.filter, ct);
        return searchedRecipes.Where(inputs.filter.Match).ToImmutableList();
    }
}

The Search method in the IRecipeService is called whenever the Term state changes, and it returns the filtered list of recipes.

2. SearchPage.xaml

Search Term:

<TextBox utu:CommandExtensions.Command="{Binding Search}"
         Style="{StaticResource ChefsPrimaryTextBoxStyle}"
         CornerRadius="28"
         PlaceholderText="Search"
         Text="{Binding Term, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

FeedView:

<uer:FeedView x:Name="SearchFeed"
              NoneTemplate="{StaticResource EmptyTemplate}"
              utu:AutoLayout.PrimaryAlignment="Stretch"
              Source="{Binding Results}">
    <DataTemplate>
        <ScrollViewer VerticalScrollBarVisibility="Hidden">
            <muxc:ItemsRepeater x:Name="SearchRepeater"
                                Margin="{utu:Responsive Narrow='16,0,16,16',
                                                        Wide='40,0,40,40'}"
                                uen:Navigation.Request="RecipeDetails"
                                ItemTemplate="{StaticResource RecipeTemplate}"
                                ItemsSource="{Binding Data}"
                                Layout="{StaticResource ResponsiveGridLayout}" />
        </ScrollViewer>

    </DataTemplate>
</uer:FeedView>

The FeedView control automatically updates the displayed list of recipes whenever the Results feed is updated, providing a dynamic and responsive search experience.

Using Custom Filter Logic

In addition to the search term, you can also maintain a filter state to refine search results further. In Chefs, the Filter property in the SearchModel defines custom filtering logic. See How to Filter Search Results Dynamically with MVUX for a closer look.

Source Code

Documentation