Improving CollectionView Scrolling

Sometimes scrolling a Xamarin.Forms.CollectionView (especially on Android) can be choppy. Let's take a look at how to fix it!

Improving CollectionView Scrolling

Background

I was first alerted by @anaseeen on Twitter that my GitTrends app had substantial jitter when scrolling the CollectionView.

I verified the jitter was also happening on my Google Pixel 3. Because the Pixel 3 has substantial CPU and RAM resources, this test confirmed that this problem is not device-specific and there is a performance issue in my code.

Solution

I found that two things were causing the UI to jitter when the user scrolls a CollectionView:

  • Bindings
  • Garbage Collection

I was able to remove the jitter by removing bindings from my DataTemplate and increasing the size of the Android Nursery which decreases the frequency of garbage collections.

If you'd like to see exactly how I solved it, here is the Pull Request that fixed it in my GitTrends app: https://github.com/brminnick/GitTrends/pull/143

Before (Substantial Scrolling Jitter) After (No Scrolling Jitter)
BeforeFix AfterFix

Removing DataTemplate Bindings

To remove bindings, we can use a DataTemplateSelector to pass the BindingContext directly into our DataTemplate and assign the values on initialization.

Before Removing DataTemplate Bindings

using Xamarin.Forms.Markup;

class CollectionViewPage : ContentPage
{
    Content = new CollectionView
    {
        ItemTemplate = new ImageDataTemplate()
    }.Bind(CollectionView.ItemsSourceProperty, nameof(CollectionViewModel.ImageList));
}

class ImageModel
{
    public string ImageTitle { get; set; }
    public string ImageUrl { get; set; }
}

class ImageDataTemplate : DataTemplate
{
    public MyDataTemplate() : base(() => CreateDataTemplate())
    {

    }

    static View CreateDataTemplate() => new StackLayout
    {
        Children = 
        {
            new Image().Bind(Image.SourceProperty, nameof(ImageModel.ImageUrl))
            new Label().Bind(Label.TextProperty, nameof(ImageModel.ImageTitle))
        }
   }
}

After Removing DataTemplate Bindings

using Xamarin.Forms.Markup;

class CollectionViewPage : ContentPage
{
    Content = new CollectionView
    {
        ItemTemplate = new ImageDataTemplateSelector()
    }.Bind(CollectionView.ItemsSourceProperty, nameof(CollectionViewModel.ImageList));
}

class ImageModel
{
    public string ImageTitle { get; set; }
    public string ImageUrl { get; set; }
}

class ImageDataTemplateSelector : DataTemplateSelector
{
    protected override DataTemplate OnSelectTemplate(object item, BindableObject container) => new  ImageDataTemplate((ImageModel)item);

    class ImageDataTemplate : DataTemplate
    {
        public MyDataTemplate(ImageModel imageModel) : base(() => CreateDataTemplate(imageModel))
        {

        }

        static View CreateDataTemplate(ImageModel imageModel) => new StackLayout
        {
            Children = 
            {
                new Image { Source = imageModel.ImageUrl },
                new Label { Text = imageModel.ImageTitle }
            }
       }
    }
}

Increase Nursery Size to Decrease Garbage Collection

We can set the size of the Android Nursery by setting it in the MONO_GC_PARAMS:

1. In the Xamarin.Android project, create a new file called GarbageCollector.config

2. In GarbageCollector.config, add the following line of code:

MONO_GC_PARAMS=nursery-size=64m
Note: The value for  nursery-size must be a power of 2 (e.g. 2, 4, 8, 16, 32, 64, 128 etc)
Note: I recommend trying different values for your nursery-size because each app is different and will have different memory requirements

3. In Visual Studio, in the Solution Explorer, right-click on GarbageCollector.config

4. In the right-click menu, select BuildAction > AndroidEnvironment

Screen Shot 2020-07-12 at 8 53 36 AM