Category Archives: Xamarin Forms

Xamarin Forms: Avoiding data binding in ListViews

FacebookTwitterGoogle+Share

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

Note: This post was inspired by Jason Smith’s Evolve session discussing performance in Xamarin Forms. Jason gives a lot of great information and I highly recommend watching the video. At one point he mentions reducing the amount of data binding, and I was asked recently how you would do that in a ListView, so here is my solution. I am not a fan of over optimizing, so I would only use this if you are seeing performance issues in your ListView (scrolling hiccups etc) along with the other techniques Jason discusses in the video.

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 a ListView. 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 the ListView (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 the ListView:

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:

image

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 custom ViewCell control, 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.

image

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 to ViewCell:

<?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 from ViewCell so 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 the ItemCell and 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 this assumes 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 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.

Xamarin Forms: A Simple Circular Progress Control

Source code for this sample is available on GitHub here: https://github.com/billreiss/xamlnative/tree/master/XamarinForms/CircularProgress

Recently in a Xamarin Forms app I needed to show progress of an operation as a circle that would fill itself in as the progress completed. Something like this:

10uk8w_thumb.gif

 

The built in Xamarin Forms Progress control is a line, and can’t easily be changed to show a circle. I looked at some different options, including finding a native control for the different platforms and render them using custom renderers, or to use a component for the component store, or something on NuGet, but didn’t really find anything suitable. So I did my own thing, and I’m really happy with how it turned out, then I wanted to share what I came up with. Feel free to riff on this design and come up with your own variations.

First I thought about vector graphics. There doesn’t seem to be any built in support for this in Xamarin Forms, and I would have to do custom renderers for each platform. Then I started thinking about using images to do the animation. It certainly would be better with vector graphics, but maybe this would be “good enough”. Then I considered how many different images I would need to make it look smooth. A dozen? More? Something like this?

th_thumb.jpg

 

* this is a copy of a circle sprite sheet published here

I was able to come up with a solution that uses two images. Actually two instances each of two images, so four images total. Combining these images with rotation and changing which images show in front of each other, we can show any percentage of progress smoothly.

The two images we need are semicircles, one for completed progress, and one for incomplete progress (you can download them here https://github.com/billreiss/xamlnative/tree/master/XamarinForms/CircularProgress/CircularProgress/CircularProgress.Droid/Resources/drawable):

progress_pending_thumb.png progress_done_thumb.png

 

These are two semicircles saved as PNG format with transparent backgrounds. First let’s consider the case between 0 and 50% complete. Let’s call the first image the “completed” image, and the second is the “pending” image. We will need three images to handle the case from 0 to 50%, one “completed” and two “pending” images.

The completed image (in the code we’ll call it “progress1”) looks like this:

image.png

 

Then on top of that we display a “pending” image (in the code we’ll call it “background1” looks like this:

image.png

 

With these directly on top of each other, the darker blue circle is completely covered and not visible.

Then we add another copy of this image, but rotated 180 degrees:

image.png

 

This makes a solid light blue circle, which is what we expect to see at 0% complete.

image.png

Then if we rotate the instance of “background1”, we will expose part of the dark blue semicircle. Part of “background1” will overlap “background2”, but that’s ok, since they are the same color you won’t be able to tell.

image.png

 

Once we get to 50%, then things change a little. Now we need 2 instances of the dark blue image, one rotated 180 degrees, and one instance of the light blue image. This light blue image will reveal portions of the 180 degree rotated dark blue image similar to how the less than 50% scenario worked, and the non-rotated dark blue image needs to be in front of this pending image, and the rotated completed image needs to behind the pending image. So the light blue image is sandwiched between the other two, with the non-rotated image all the way to the front, otherwise as the pending image rotates it would obscure this “half-completed” part of the progress.

Are you totally confused yet? Thankfully it’s actually not a lot of code. We will have a dependency property called “Progress” that goes from 0 to 1 to match how the built-in progress control works. And then when that value changes, we do the right thing showing and rotating images to get the right effect.

IMPORTANT NOTE: If you create your Xamarin Forms project from scratch using the built in Visual Studio template, make sure you use NuGet to update Xamarin Forms to the latest stable version. There was a bug in Xamarin Forms on Windows platforms that would make this code not have the desired result.

Here is the code for the circular progress control:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace CircularProgress
{
    public class CircularProgressControl : Grid
    {
        View progress1;
        View progress2;
        View background1;
        View background2;
        public CircularProgressControl()
        {
            progress1 = CreateImage("progress_done");
            background1 = CreateImage("progress_pending");
            background2 = CreateImage("progress_pending");
            progress2 = CreateImage("progress_done");
            HandleProgressChanged(1, 0);
        }

        private View CreateImage(string v1)
        {
            var img = new Image();
            img.Source = ImageSource.FromFile(v1 + ".png");
            this.Children.Add(img);
            return img;
        }

        public static BindableProperty ProgressProperty =
    BindableProperty.Create("Progress", typeof(double), typeof(CircularProgressControl), 0d, propertyChanged: ProgressChanged);

        private static void ProgressChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var c = bindable as CircularProgressControl;
            c.HandleProgressChanged(Clamp((double)oldValue, 0, 1), Clamp((double)newValue, 0, 1));
        }

        static double Clamp(double value, double min, double max)
        {
            if (value <= max && value >= min) return value;
            else if (value > max) return max;
            else return min;
        }

        private void HandleProgressChanged(double oldValue, double p)
        {
            if (p < .5)
            {
                if (oldValue >= .5)
                {
                    // this code is CPU intensive so only do it if we go from >=50% to <50%
                    background1.IsVisible = true;
                    progress2.IsVisible = false;
                    background2.Rotation = 180;
                    progress1.Rotation = 0;
                }
                double rotation = 360 * p;
                background1.Rotation = rotation;
            }
            else
            {
                if (oldValue < .5)
                {
                    // this code is CPU intensive so only do it if we go from <50% to >=50%
                    background1.IsVisible = false;
                    progress2.IsVisible = true;
                    progress1.Rotation = 180;
                }
                double rotation = 360 * p;
                background2.Rotation = rotation;
            }
        }

        public double Progress
        {
            get { return (double)this.GetValue(ProgressProperty); }
            set { SetValue(ProgressProperty, value); }
        }
    }
}

We also need to put the images in the correct images folder for each platform. For iOS this is the Resources folder, and the Build Action should be set to BundleResource. For Android it’s the Resources/drawable folder and the type is AndroidResource. For the Windows platforms they go in the root folder.

Then to use the control. Assume we have a Xamarin Forms XAML page called MainPage.xaml, we can load the control 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="CircularProgress.MainPage" 
             xmlns:local="clr-namespace:CircularProgress" BackgroundColor="White">
  <Grid>
    <local:CircularProgressControl x:Name="progressControl" Progress="0" HorizontalOptions="Center" VerticalOptions="Center" WidthRequest="60" HeightRequest="60"/>
  </Grid>
</ContentPage>

And in the code behind we can have a timer that increments the Progress property a bit at a time from 0 to 1 and then starts over:

namespace CircularProgress
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            Xamarin.Forms.Device.StartTimer(TimeSpan.FromSeconds(.02), OnTimer);
        }

        private bool OnTimer()
        {
            var progress = (progressControl.Progress + .01) ;
            if (progress > 1) progress = 0;
            progressControl.Progress = progress;
            return true;
        }
    }
}

Now if you run the app you should see something like the animated image at the top of this post. No custom renderers and it runs on every Xamarin Forms platform!