Advanced ListView Bindings

The traditional Binding

When first making contact with ListViews in an Xamarin Forms MVVM design you usually publish an ObservableCollection as a property of the ViewModel of your Page, bind the ItemSource of the ListView to it and then bind to the items in´the collection in your Datatemplate. Like this:

    [ImplementPropertyChanged]  //I use Fody here to not have to bother with INotifyPropertyChanged
    public class DemoListViewPageModel
    {
        public ObservableCollection<Dog> DogList

        public DemoListViewPageModel()
        {
            DogList = new ObservableCollection<Dog>();

            DogList.Add(new Dog() { Name = "Rex", Race = "German Sheppard" });
            DogList.Add(new Dog() { Name = "Barney", Race = "Poodle" });
            DogList.Add(new Dog() { Name = "Jimmy", Race = "Beagle" });
            DogList.Add(new Dog() { Name = "Rob", Race = "Labrador" });
        }
    }

    // Should be something that makes a bit of sense, so why not dogs
    [ImplementPropertyChanged]
    public class Dogs
    {
        public string Name {get; set}

        public string Race {get; set;}
    }

And in the Page then

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Examples.DemoListViewPage">
  <ContentPage.Content>
    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
      <ListView ItemsSource="{Binding DogList}" HasUnevenRows="True">
        <ListView.ItemTemplate>
          <DataTemplate>         
            <ViewCell>
              <StackLayout HorizontalOptions="Start" >
                <Label x:Name="Name"  Text="{Binding Name}"  TextColor="Navy" FontSize="20"/>
                <Label x:Name="Race" Text="{Binding Race}"/>
              </StackLayout>
            </ViewCell>           
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>
  </ContentPage.Content>
</ContentPage>

That’s fine but

  • It violates MVVM in exposing the model behind it because the DataTemplate binds to model objects
  • The moment we want to store additional data for each item or execute a command from an ViewCell it get’s difficult

While the first might seem be an academic reason it really couples the model tightly to the view which makes changes in the model layer propagate to the view.

Let me elaborate on the second drawback.

  • Imagine we add a check mark (let’s asume we have such a control) to the ViewCell. Now we have to keep track of it’s state somewhere. We could bind its Toggle command to a Command in our ViewModel and also add a List<bool> checkedItems where we store the state for each item in the ListView.
  • We want to add a Add to Shopping Cart Button to our ViewCell. Again we could bind all Buttons of the ListView to a Command in our ViewModel that calls a WebService to add the item to the shopping cart. (Ok this example might be somewhat contrived)

Both both enhancements need additional code in the ViewModel to figure out which item the Command should be exceuted on which should not be the job of the ViewModel. The real reason for the problem is that our ViewModel is the ViewModel for the whole Page. The ListView is a container that holds a list of ItemViews that are defined in the DataTemplateof the ListView. So the consequence should be: Give each ItemView its own ViewModel. Which means our page’s ViewModel no longer exposes a ObservableColletion<Dogs> but ObservableColletion<DogsItemViewModel>

Introducing ViewModels for each ListView item

public class DogsItemViewModel
{
    private Dog DogObject;
    ICommand AddToCartCommand;

    public DogsItemViewModel(Dog theDog)
    {
        DogObject = theDog;
        AddToCartCommand = new Xamarin.Forms.Command(AddToCart);
    }

    public string Name => DogObject.Name;
    public string Race => DogObject.Race.ToUpper();
    public bool IsChecked { get; set; }

    void AddToCart()
    {
        WebService.Instance.AddToCart(DogObject);  // Whatever this will do ;-) 
    }
}

Now the DogsItemViewModel can handle all interaction with the ItemView on it’s own. You can even do conversions on the model data without the need to write an ValueConverter like I did with the Race property.

Now we refactor our ViewCell and make it a custom class. From it’s code behind file you can now even access it’s elements using x:Name

<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="YourNameSpace.DogItemView">


  <StackLayout HorizontalOptions="Start" >
    <CheckBox State = "{Binding IsChecked}" />
    <Label x:Name="Name"  Text="{Binding Name}"  TextColor="Navy" FontSize="20"/>
    <Label x:Name="Race" Text="{Binding Race}"/>
    <Button Text="Add to Cart" Command="{Binding AddToCartCommand}"/>
  </StackLayout>
</ViewCell>

and our Page like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Examples.DemoListViewPage">
  <ContentPage.Content>
    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
      <ListView ItemsSource="{Binding DogList}" HasUnevenRows="True">
        <ListView.ItemTemplate>
          <DataTemplate>         
            <DogItemView BindingContext = "{Binding}" />
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>
  </ContentPage.Content>
</ContentPage>

Looks cool, doesn’t it?

Contact me:

Leave a Reply

Your email address will not be published. Required fields are marked *