Xamarin.Forms/MAUI CollectionView performance issue: it's your own damn fault.

Xamarin.Forms/MAUI CollectionView performance issue: it's your own damn fault.

So I have seen a lot of people complaining about the performance of the CollectionView on Xamarin.Forms and soon it will be the same with Maui...

If you have a laggy list, it's true there are some issues with the implementation of vanilla XF CollectionView (especially with DataTemplateSelector and the recycling of the selected DataTemplate...),

But I'll bet it's your own damn fault.

The collection view doesn't magically optimize all of your bloated views, you have to follow some basic rules if you want a slick smooth scrolling...

So here are 4 golden rules to have a smooth scroll with your collections.

USE FIXED HEIGHT FOR YOUR ITEMS !!!!

This is the most important one, if you don't specify a fixed height for your items, each time a view will be recycled, it will make a whole layout calculation on your view to compute its size! This is nuts.

Oh... Oh But I know what you will say:

"boohooh, but my designer wants that all cells size automatically depending on its content, boohooohhh, cry, cry...".

Stop that! Stay strong folks! Stand-up to your designer!

Are you a man or a mouse?

Then tell him: "ok! no pro bro :-) it will size automatically dude <3 ...but you will have a GODDAMN SHITTY LAGGING LIST!"

After that your designer should soften and you can make peace with him over a fixed size per DataTemplateSelector's templates by using the Sharpnado.CollectionView:

GitHub - roubachof/Sharpnado.CollectionView: A performant list view supporting: grid, horizontal and vertical layout, drag and drop, and reveal animations.
A performant list view supporting: grid, horizontal and vertical layout, drag and drop, and reveal animations. - GitHub - roubachof/Sharpnado.CollectionView: A performant list view supporting: grid...
<views:HeaderFooterGroupingTemplateSelector
    x:Key="HeaderFooterGroupingTemplateSelector"
    DudeTemplate="{StaticResource DudeTemplate}"
    FooterTemplate="{sho:SizedDataTemplate 
                    Template={StaticResource FooterTemplate},
                                             Size=60}"
    GroupHeaderTemplate="{sho:SizedDataTemplate 
                         Template={StaticResource GroupHeaderTemplate},
                                                  Size=75}"
    HeaderTemplate="{sho:SizedDataTemplate 
                    Template={StaticResource HeaderTemplate},
                                             Size=40}" />

If you can live with a fixed size of all the items, you can use the vanilla CollectionView but don't forget to specify the MeasureFirstItem value for your sizing strategy.

Careful with Data Binding

Data binding is in fact, very costly, and very often you don't set your binding mode explicitly. And then it defaults to OneWay, which means that it will listen to all changes coming from your source (your view model).

BUT, most of the time, you will display readonly data on your list, so use OneTime binding mode instead, you silly

One time binding documentation
an example of one-time binding on a list item

Don't forget also to use compiled binding on your data template, it will also boost significantly your binding performance (and give you intellisense on your item views).

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.CompiledColorListPage"
             Title="Compiled Color List">
    <Grid>
        ...
        <ListView x:Name="colorListView"
                  ItemsSource="{x:Static local:NamedColor.All}"
                  ... >
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:NamedColor">
                    <ViewCell>
                        <StackLayout Orientation="Horizontal">
                            <BoxView Color="{Binding Color}"
                                     ... />
                            <Label Text="{Binding FriendlyName}"
                                   ... />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <!-- The BoxView doesn't use compiled bindings -->
        <BoxView Color="{Binding Source={x:Reference colorListView}, 
                        Path=SelectedItem.Color}"
                 ... />
    </Grid>
</ContentPage>

Update your whole item, not some properties

If the list your using is implemented with a RecyclerView on Android and UICollectionView on iOS (which is the case of Sharpnado.CollectionView and vanilla CollectionView), it will be much much more efficient to replace the whole item, than updating some properties through binding.

The swap, insertion, or whatever change, will then be handled at the native level, even with a nice (insertion/deletion) animation.

If you only change one item at a time, use the ObservableCollection if you are changing several items (batch deletion or insertion), use the ObservableRangeCollection in the sharpnado's package, or the one in the XamarinCommunityToolkit.

public ObservableCollection<TourSummary> TourSummaries { get; set; } 
    = new();

private void OnTourSummaryUpdated(TourSummary tourSummary)
{
    int index = _tourSummaries.IndexOf(c => c.Id == tourSummary.Id);
    if (index > -1)
    {
        // replacing your item
        TourSummaries[index] = tourSummary;
    }
    else
    {
        // adding at the end
        TourSummaries.Add(tourSummary);
    }
}

It's even easier from a business point of view, instead of having UpdateTourDate, UpdateTourDescription, etc, methods in your service layer, you just need a UpdateTour method, updating the whole object.

View nesting

Careful with nesting two many views, prefer flat views with Grid layout, but if you are StackLayout nesting addict, don't forget that you can compress all your nested views into a flat view thanks to the CompressedLayout.IsHeadless property!

using compressed layout