A search button should not have to rewrite labels and list cells one at a time. Keeping screen state in a ViewModel and binding the View to it separates search behavior from visual controls. That MVVM boundary is a useful way to explain the MAUI structure in my Kitaplık archive alongside its repository and API layers.
Give the ViewModel a clear source and result
The example uses three in-memory book titles instead of a real API. Search holds the input, and Books holds the displayed result. This isolates binding behavior from network and authentication concerns. It requires a MAUI project and the CommunityToolkit.Mvvm 8.3+ generator API shape.
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class BooksViewModel:ObservableObject
{
private readonly string[] source=["Algorithms","Clean Code","Refactoring"];
[ObservableProperty] private string search="";
public ObservableCollection<string> Books {get;}=new();
public BooksViewModel()=>Filter();
[RelayCommand]
private void Filter()
{
Books.Clear();
foreach(var title in source.Where(t=>t.Contains(Search,StringComparison.OrdinalIgnoreCase)))
Books.Add(title);
}
}
// Page XAML: bind Entry.Text to Search, Button.Command to FilterCommand,
// and CollectionView.ItemsSource to Books; set BindingContext to BooksViewModel.
ObservableProperty generates Search and its change notification from the search field. RelayCommand exposes Filter as FilterCommand. The class is partial so generated code can extend it. ObservableCollection reports Clear and Add operations to the bound View; mutating an ordinary List does not automatically supply equivalent collection notifications.
Bind to the generated command
<VerticalStackLayout xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
<Entry Text="{Binding Search}" Placeholder="Search books" />
<Button Text="Filter" Command="{Binding FilterCommand}" />
<CollectionView ItemsSource="{Binding Books}">
<CollectionView.ItemTemplate>
<DataTemplate><Label Text="{Binding .}" /></DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
// Inside the page constructor, after InitializeComponent():
BindingContext = new BooksViewModel();
A wrong BindingContext can make correctly named bindings appear ineffective. Verify that the intended ViewModel is actually assigned before debugging the filtering logic. Compiled bindings with x:DataType can catch certain property-name mistakes at build time instead of leaving them as runtime binding problems.
The compact XAML demonstrates binding, not a complete large-list layout. A real scrolling CollectionView should have a constrained space, such as an appropriate Grid row. An unconstrained vertical stack can interfere with measurement and virtualization expectations. A three-item screen is not enough to validate that layout for a large catalog.
Changing the query is not the same as running the command
Setting Search to code and executing FilterCommand leaves Clean Code. Merely typing does not invoke the command in this sample. Live search needs a property-change hook or another explicit event flow. Empty text matches the source titles; null and surrounding whitespace should be normalized at the actual input boundary.
The source array remains unchanged while the visible collection is rebuilt. Clearing the search can therefore restore all books. Repeatedly filtering the already filtered result would gradually discard the apparent source and produce confusing resets.
Network search adds state and ordering concerns
An API-backed screen needs separate loading, empty-success and failure states. A failed connection should not be described as no matching books. An asynchronous command can prevent duplicate submissions, while cancellation or a query version prevents an older response from replacing the newest search.
Update ObservableCollection on the UI thread. Wrapping an HTTP call in Task.Run is not an automatic optimization; asynchronous I/O can already be awaited. ViewModel tests can use a fake repository to exercise results and failures without driving the device interface for every business scenario.
Choose the page and ViewModel lifetime intentionally
A new ViewModel on each navigation may reset the query, while reusing one can preserve stale results. Decide that DI lifetime against the desired screen behavior, and define what happens to pending searches and subscriptions when the page closes. Barcode input is another feature feeding the book form, not a reason to mix camera lifecycle into this list command.
Expect three initial results, one for code, none for an unmatched query and three again after clearing it. Then try a delayed and failing repository. These checks make it possible to distinguish a binding failure from a data retrieval failure.
ObservableProperty generator; RelayCommand generator; CollectionView layout.