Xamarin.Forms SearchBar with Mvvm
Wow - it's been a long time since my last post. I've been very busy with something that I hope to chat about before too long...
In the meantime, here are some thoughts on using the Xamarin.Forms SearchBar control with Mvvm.
Firstly, there's a great, simple explanation of the control itself here: https://www.syntaxismyui.com/xamarin-forms-searchbar-recipe/
This shows how to get up and running without Mvvm, applying a search filter form the TextChanged event of the SearchBar. When you put this into an Mvvm structured app there are a couple of gotchas to look out for. You can bind the Text property of the SearchBar to an Mvvm compatible string property in your ViewModel, and the setter replaces the TextChanged event on the above link, like this:
<SearchBar Text="{Binding SearchText}"
SearchCommand="{Binding SearchCommand}"
/>
Where your SearchText definition in your associated view model would look something like this:
private string _searchText;
public string SearchText
{
get { return _searchText; }
set { if (_searchText != value) { _searchText = value; RaisePropertyChanged(() => SearchText); } }
}
You can now set up the ObservableCollection property in your viewmodel which you have bound your list to looks something like this:
public ObservableCollection<YourEntity> YourList
{
get
{
ObservableCollection<YourEntity> theCollection = new ObservableCollection<YourEntity>();
if (_yourList != null)
{
List<YourEntity> entities = (from e in _yourList
where e.dealerDisplayName.Contains(_searchText)
select e).ToList<YourEntity>();
if (entities != null && entities.Any())
{
theCollection = new ObservableCollection<YourEntity>(entities);
}
}
return theCollection;
}
}
(This assumes that you have other code to populate _yourList in the first place, by reading from a DB or perhaps calling a web service.)
The SearchBar also supports command binding (as you can see from the xaml above), so we can define our search command like this:
#region Command and associated methods for SearchCommand
private Xamarin.Forms.Command _searchCommand;
public System.Windows.Input.ICommand SearchCommand
{
get
{
_searchCommand = _searchCommand ?? new Xamarin.Forms.Command(DoSearchCommand, CanExecuteSearchCommand);
return _searchCommand;
}
}
private void DoSearchCommand()
{
// Refresh the list, which will automatically apply the search text
RaisePropertyChanged(() => YourList);
}
private bool CanExecuteSearchCommand()
{
return true;
}
#endregion
Now let's embellish our property a little to get it to execute the command each time the text is changed. (This is effectively the Mvvm equivalent of the TextChanged event in the blog post referenced above.)
public string SearchText
{
get { return _searchText; }
set
{
if (_searchText != value)
{
_searchText = value;
RaisePropertyChanged(() => SearchText);
// Perform the search
if (SearchCommand.CanExecute(null))
{
SearchCommand.Execute(null);
}
}
}
}
All well and good. However, there are two gotchas here. This code, as it stands, will fail. The problem is that the first time in, _searchText is not an empty string - it's null. This upsets our Linq statement. We can remedy this by changing:
private string _searchText;
to:
private string _searchText = string.Empty;
First time in things now work fine. You should also be able to apply filtering to your list and watch it in action. However, there's one more gotcha waiting in the wings. The SearchBar comes with a built in 'Cancel' button, which you can use to clear your search text. Your users will expect this to remove all filtering and display the entire list again. However, clicking the 'Cancel' button does not set the Text property of the SearchBar to an empty string - you guessed it, it sets it to null. This upsets our Linq statement all over again.
We can remedy this by changing the setter of our property slightly, to ensure that if an attempt is made to set it to null, it will simply set it to an empty string instead. We'll change this:
_searchText = value ;
to this:
_searchText = value ?? string.Empty;
It should now be happy again, and ready to roll.