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.
@JamesMontemagno hi I had a bindable pucker with All,Open and Close as a items which is binding using MVVM pattern with Itemsource property.But I want to filter the list view based on the bindable picker view selection changed.may I know how i know the event fired in ViewModel
— Sylendra (@sylendra7) July 28, 2018
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:
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:
public ObservableRangeCollection<string> FilterOptions
to display all of our filter options.public string SelectedFilter
to bind to and filter when changed.void FilterItems()
which we will implement a simple filtering on.
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.