Xamarin.forms: [Bug] [Android] CollectionView jittering on Android with GridItemsLayout

Created on 29 Nov 2019  Â·  32Comments  Â·  Source: xamarin/Xamarin.Forms

Description

I tried to reproduce UI of one popular music app (DI.FM).
During implementation of page with styles I have encountered issue with collectionview and images. Fast scroll on CollectionView with lots of images produces jittering on Android.
I have tried Image, ffImageLoading:CachedImage (a bit better), Image with GlideX(a bit better).
While it works perfectly smooth on ios even with ordinary Image control. Also I have tried different sizing strategies.

Steps to Reproduce

  1. Start reproduction project on Android and open RadioChannels "Popular" tab or just "Popular" page from side menu. (Same on ios for comparison)

Expected Behavior

No jittering, smooth scrolling

Actual Behavior

Jittering

Basic Information

  • Version with issue:
  • Nuget Packages: Xamarin.Forms 4.3.0.991211
  • Affected Devices: Android devices and emulators

Reproduction Link

DIFM.zip

collectionview performance 5 high impact Android bug

Most helpful comment

Workaround

I created a blog post regarding this:
https://codetraveler.io/2020/07/12/improving-collectionview-scrolling/

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 to decrease the number 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 }
            }
       }
    }
}

Increate 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: 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

  1. In Visual Studio, in the Solution Explorer, right-click on GarbageCollector.config
  2. In the right-click menu, select BuildAction > AndroidEnvironment

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

All 32 comments

I don't see anything weird with how the images are loaded. Images are not too big. Yet the scrolling is not very smooth.

Yes. But on ios scrolling is very smooth.
Also, you can download the original app from PlayMarket to check the same page. Scrolling is very smooth on Android in the original app with the approximately same amount of images. An original app was written with Xamarin.Native

Same problem here...
Scrolling jitters a lot on Android...
Any news for this issue?

@Alex-111 Tried latest stable version and still the same. But a preview of 4.6.0 looks a bit better

Also just tried the preview of 4.6. Unfortunately not much change here….
Also wanted to point out, that it has not only to do with grid layout. Also other layouts are affected. Seems it has something to do how collectionview renders the elements...

Hello. Same problem on my side... Even with LinearItemsLayout
I've tried: FFIL (better) and GlideX (really better).
In the output console, we can see many garbage collections even on a simple project.

After analysis with Xamarin Profiler, it seems there's a lot of BindableProperty and linked mechanisms created in memory when scrolling (BindablePropertyContext, ...). I guess, that's a consequence of recycling...

When i don't use DataBinding, things are better but not perfects... Not relying on DataBinding specially with images is advised by the FFIL author for example....
You should try even if it's not cool...

I've also seen the same kind of problems with ListView...

If someone has some best practices/solutions on this topic, it will be really appreciated :)

I can confirm this. Lots of garbage collections in the debug output… But not sure if this alone is the reason for the heavy jitter..
Unfortunately, it is event not smooth on high end smartphones like Samsung S20, which is not accepted by customers...

Any progress on this issue? This is causing serious issues within our project and we can't release due to this issue

Latest version in combination with GlideX.Forms looks pretty good but still not perfect as iOS version of CollectionView

@JMNewsome Don't expect the Xamarin.Forms team to be quick on resolving issues...

Any news on this? This one makes developing on Android really painful....

I'm seeing the same behavior in my GitTrends app.

Here's a video one of the users posted on Twitter running GitTrends v1.2.0:
https://twitter.com/anaseeen/status/1281610967336595458?s=20

  • 4GB RAM
  • Media Tek Helio octa core P23
  • Android 9

GitTrends v1.2.0 Source Code:
https://github.com/brminnick/GitTrends/releases/tag/v1.2.0

Yeah, this is a very big performance problem, can we get some kind of higher priority on this item?

Workaround

I created a blog post regarding this:
https://codetraveler.io/2020/07/12/improving-collectionview-scrolling/

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 to decrease the number 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 }
            }
       }
    }
}

Increate 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: 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

  1. In Visual Studio, in the Solution Explorer, right-click on GarbageCollector.config
  2. In the right-click menu, select BuildAction > AndroidEnvironment

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

Thank you @brminnick . I will try this.
Concerning the code refactoring Binding => Assignment, create a DataTemplate in code-behind seems to me a little bit unusual (especially with more complex cells). But I guess, same result could be obtained with Xaml and OnBindingContextChanged overriding. DataBinding is however powerfull and that's too bad that we cannot use it properly here :(. Thanks again

@brminnick : Thanks for suggesting some workarounds!

  • regarding the nursery-size: This alone had no effect in my case - unfortunately….

  • regarding the binding: In your case you do not update the view on data change. This is needed in my case. So unfortunately we cannot use your workaround...

any further suggestions are appreciated...

I am in the same situation as @Alex-111 ...
I had nursery-size and soft-heap-limit already 128M, that didn't change anything.
My DataTemplates are updating the the view as well on PropertyChanges and are a bit more complex. Binding makes those changes easy and they are working perfectly on iOS and UWP... There must be another solution/workaround...

I found here: https://www.sharpnado.com/gittrends-lags/ that the GC Major collection is always growing.
In the test I made, I start from the GitTrends app repo with the CollectionView displaying empty frames.
After 1 min of scrolling back and fourth, the GC major collection grows from 4352k to 44512k

In our side: complex cells: one big image + up to 5 small images (stickers) and more or less complex controls.
-Using GlideX
-Removing all bindings (previously ~30 by cell (controls and sub controls))
-Setting MONO_GC_PARAMS=nursery-size=512m (I know that's a lot...)

We achieved to have no more scroll lags on performant devices=> No more Garbage Collections in output
We've seen an enhancement on other (older) devices and no negative impact (regarding the nursery size)

Like we observed previously, Garbage collections are caused by intensive objects creations (BindableProperty mechanisms): not sure about any memory leak...

Thanks @brminnick

@rudyspano setting the nursery to 512m is like disabling GC...
So of course you won't have gc issues...
...until you reach the 512m limit and you will experience something really weird cause a collection of a 512m nursery is not something you see every day.
It's also probable that android will kill your app before even reaching the nursery size limit.

Thanks @roubachof , I will check with a more acceptable value.

I think this garbage collection issue is the number one factor in Xamarin Forms for Android running terribly and should be something that is prioritized.

It's pretty sad to see Xamarin go down this path. The listview is not recommended any more and collection view is pretty much not usable. As a developer I dont know how am I gonna use xamarin to build apps, or even support the apps that we have build over the years.

I agree, what I love about Xamarin is the ability to use C# and shared between the platform, code execution speed for me on a compiled app is NOT the problem. The problem is the UI thread, which of course will cause everything else to appear to run bad. If the UI thread didn't feel like it was held together with duct tape and staples, It would be so much better

@brminnick, is this an issue when creating UI controls from code behind? I tested this out with one of my CollectionView templates. Creating UI control from code behind causes the Nursery to become full frequently, in turn calling the GC constantly. Using XAML, Nursery full warning is less frequent.

@Deepfreezed Can you share your reproduction code and its associated performance testing results?

As far as I'm aware, this is not a XAML-specific nor a C#-specific problem. Rather, it is a problem related to bindings and garbage collection.

Same issue is here for me. Android performance is very bad.

Will the issue be resolved in the next release of Xamarin Forms? Due to this issue, we are unable to distribute the application.

This problem is really very serious, I changed the listview for collectionview by official recommendation, now a problem like this arises. It is very sad to see that it has been over a year and the problem has not been solved, the solution @brminnick does not sound very good to me due to all the efforts made, but it solved this problem in a demo I made, we have an application with 5 million downloads made in xamarin and we often have a crash and only now I discovered that this was the real problem, what a shame.
That is unfortunate 👎

I had this problem on a production app. I need this fix in the 5.0 milestone.

I replaced the CollectionView with a StackLayout and Datatemplate. This works for me.

@AlleSchonWeg Replacing a CollectionView with a StackLayout and Datatemplate is not a proper solution as it does not support virtualization, so you will see an impact the more items you have

Was this page helpful?
0 / 5 - 0 ratings