James Montemagno
James Montemagno

Live, Love, Bike, and Code.

Live, Love, Bike, and Code

Share


Tags


Xamarin.Forms: Filtering a ListView with a Bindable Picker

In this edition of "Ask Motz", I will solve a very common problem when working with lists of data and how to filter them. I was recently asked how to do this completely with MVVM and data bindings without having any code behind in the page.

The nice thing about the Xamarin.Forms Picker is that it has all sorts of great bindable properties including ItemsSource, SelectedIndex, and SelectedItem!

Given a list of items that have a Role property assigned to them we would like to have a picker that allows us to filter the list that is shown. Here is what we are going to build:

Untitled-Project-1

Update the Model

I took the standard Master-Detail project template that ships with Xamarin.Forms and added a Role property onto the Item:

public class Item
{
    public string Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
    public string Role { get; set; }
}

I also create a mock list of items that are returned from my data store to have the following roles: Admin, Editor, Student. We will use these as a filter:

 var mockItems = new List<Item>
{
    new Item { Text = "First item", Role = "Admin" },
    new Item { Text = "Second item", Role="Admin" },
    new Item { Text = "Third item", Role="Editor" },
    new Item { Text = "Fourth item", Role="Editor" },
    new Item { Text = "Fifth item",  Role="Student" },
    new Item { Text = "Sixth item",  Role="Student" },
};

Update the ViewModel

A normal ViewModel may have a property that is bound to the ItemsSource in our ListView: ObservableCollection<Item> Items {get;}. Since we need to create a filterable list we want to create another set called AllItems that acts as our master list. We will also update these to use ObservableRangeCollection from my MvvmHelpers library as it will make our lives easier.

We will also create two new properties and a method for filtering:

Here is the full code so far:

public ObservableRangeCollection<Item> Items { get; set; }
public ObservableRangeCollection<Item> AllItems { get; set; }
public ObservableRangeCollection<string> FilterOptions { get; }

string selectedFilter = "All";
public string SelectedFilter
{
    get => selectedFilter;
    set
    {
        if (SetProperty(ref selectedFilter, value))
            FilterItems();
    }
}

public ItemsViewModel()
{
    Title = "Browse";
    Items = new ObservableRangeCollection<Item>();
    AllItems = new ObservableRangeCollection<Item>();

    FilterOptions = new ObservableRangeCollection<string>
    {
        "All",
        "Admin",
        "Editor",
        "Student"
    };

    LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
}

void FilterItems()
{
}

Implementing the Filter

The filter itself is actually really simple. We want to replace the current list of items with the select filter or if it set to All then simply add all of them:

void FilterItems()
{
    Items.ReplaceRange(AllItems.Where(a => a.Role == SelectedFilter || SelectedFilter == "All"));
}

Whenever we change our SelectedFilter we will call this method, and whenever we load our data we will also call it:

async Task ExecuteLoadItemsCommand()
{
    if (IsBusy)
        return;

    IsBusy = true;

    try
    {
        var items = await DataStore.GetItemsAsync(true);
        AllItems.ReplaceRange(items);
        FilterItems();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
    finally
    {
        IsBusy = false;
    }
}

Create & Bind Picker

The last part is to simply add in our Picker control and setup the bindings:

 <StackLayout>
    <StackLayout Orientation="Horizontal" Padding="10">
        <Label Text="Filter Items:" 
               VerticalOptions="Center"/>
        <Picker ItemsSource="{Binding FilterOptions}" 
                SelectedItem="{Binding SelectedFilter}" 
                VerticalOptions="Center" 
                HorizontalOptions="FillAndExpand" />
    </StackLayout>
    <ListView x:Name="ItemsListView" 
              ItemsSource="{Binding Items}"/>
</StackLayout>

There you have it! Your new fancy bindable Picker is all setup and ready to filter that list! You can apply this same principal to any other type of filtering too.

Copyright © James Montemagno 2018 All rights reserved. Privacy Policy

View Comments