Xamarin Forms: Avoiding data binding in ListViews

Datetime:2016-08-23 03:57:00          Topic:          Share

Source code for the completed version of this sample is available on GitHub here: https://github.com/billreiss/xamlnative/tree/master/XamarinForms/ListViewNoBinding

Data binding is great. It is convenient, promotes good practices of separation of concerns, reduces glue code, and many other things. It’s also not free. It’s actually pretty expensive since it relies on Reflection, and has memory and CPU performance implications. Hopefully someday Xamarin Forms will support compiled bindings like the Universal Windows Platform. Compiled bindings remove a lot of these issues, if you are doing native UWP development, definitely give them  a look.

These negative impacts can multiply when displaying items in aListView. There is a lot more data binding going on, since it is once per row. There are also row reuse strategies that may lead to more data binding while the user scrolls, which is when you want the best performance.

There are clear trade-offs here, but let’s assume you want the optimum performance even if it’s a bit more code intensive. How would you even do this inside a ListView item template? One way is to embed a ContentView and set its display properties in code.

Let’s say we have an app displaying a list of ItemViewModel as a ListView . Here is the ItemViewModel:

public class ItemViewModel
{
    public int Id { get; set; }
    public string Text { get; set; }
}

The XAML for displaying theListView(using bindings) looks like this:

<ListView x:Name="listView" CachingStrategy="RecycleElement">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="50"/>
              <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Label x:Name="idLabel" Text="{Binding Id}"/>
            <Label x:Name="textLabel" Grid.Column="1" Text="{Binding Text}"/>
          </Grid>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>

And the code behind to populate theListView:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        listView.ItemsSource = GetItems();
    }

    public List<ItemViewModel> GetItems()
    {
        var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum".Split(' ');
        var items = new List<ItemViewModel>();
        for (int i = 0; i < 500; i++)
        {
            items.Add(new ItemViewModel() { Id = i, Text = lorem[i % lorem.Length] });
        }
        return items;
    }
}

To populate some sample code, I’m just taking a lorem ipsum string and assigning a word to each row, restarting when the words run out. This is what the app looks like when running:

Now data binding two properties may not be a major performance hit, but consider more complex item templates. So how could we accomplish the same results without data binding? Where would the code go to pump the data into the item view cell? One way would be with a custom renderer, but then you would need to implement this on each platform. Another way is to create a customViewCellcontrol, that is what we will do here.

First let’s refactor what we currently have, still using data binding, but moving the XAML for the ViewCell to its own class. In the Portable project, I can add a new Forms Xaml Page and call it ItemCell .

This generates a XAML file for layout and a code behind file. In ItemCell.xaml, we can modify it as follows, copying in the XAML from the item template and changing the outer type toViewCell:

<?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="ListViewNoBinding.ItemCell">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="50"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Label x:Name="idLabel" Text="{Binding Id}"/>
    <Label x:Name="textLabel" Grid.Column="1" Text="{Binding Text}"/>
  </Grid>
</ViewCell>

And for the ItemCell.xaml.cs code behind we need to tell it to inherit fromViewCellso that it matches the XAML file :

public partial class ItemCell : ViewCell

Finally we can change the MainPage to use this custom ViewCell:

<ListView x:Name="listView" CachingStrategy="RecycleElement">
  <ListView.ItemTemplate>
    <DataTemplate>
      <local:ItemCell/>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

Running this again, we get the same result as before, but now we have somewhere to put some code. Let’s see how we can remove the bindings in theItemCelland update the view manually. In ItemCell.xaml, remove the bindings from the Label.Text properties:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="50"/>
    <ColumnDefinition Width="*"/>
  </Grid.ColumnDefinitions>
  <Label x:Name="idLabel" />
  <Label x:Name="textLabel" Grid.Column="1" />
</Grid>

Now the other thing we need to do is update the text when the data changes. Even though we aren’t using data binding ourselves, the BindingContext of the ViewCell is set to an item in the list. Since this ListView recycles cells, a limited number of ItemCell objects are created and they are reused, each time the BindingContext is set to the current row that the cell is being used for. So to avoid data binding inside the cell, and to set the text ourselves, we can override the BindingContextChanged method on the ItemCell , and inside there set the text.

public partial class ItemCell : ViewCell
{
    public ItemCell()
    {
        InitializeComponent();
    }

    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
        var itm = BindingContext as ItemViewModel;
        if (itm != null)
        {
            idLabel.Text = itm.Id.ToString();
            textLabel.Text = itm.Text.ToString();
        }
        else
        {
            idLabel.Text = textLabel.Text = "";
        }
    }
}

Now if you run the app again, it should behave just as before, but we now have full control over when the data is refreshed and by doing it manually we are saving on Reflection costs. Note that this assumes that the data is read only, we are not listening for property changed events on the Id and Text properties of the ViewModel . You could add this by wiring and unwiring the PropertyChanged event in the OnBindingContextChanged method. I will cover this in the next post.

One last thing is that it can be interesting to set breakpoints or add debug statements to the constructor and OnBindingContextChanged of the ItemCell . It can give you some insight into how row caching works in a Xamarin Forms ListView .