roubachof/Sharpnado.CollectionView

Setting ViewCacheSize on Droid throws exceptions

Closed this issue · 8 comments

This might not be a Sharpnado bug, but my ignorance.

We have a "first scroll" issue. I found this in readme.md, no further documentation anywhere, and I'm not sure if the property goes directly to RecyclerView or if you're doing something with it.

/// In certain scenarios, the first scroll of the list can be smoothen
/// by pre-building some views.
public int ViewCacheSize { get; set; } = 0;

I'm unable to do anything with that property. Setting it in XAML throws exceptions, setting it codebehind throws exceptions. Examples:

Set in XAML:
Android.Util.AndroidRuntimeException: 'Only the original thread that created a view hierarchy can touch its views.'

Set in codebehind to a random large number:
System.ArgumentException: 'An item with the same key has already been added. Key: Xamarin.Forms.BindableProperty'

It seems that the value needs to be smaller than the number of items in ItemsSource. But we're living in an MVVM world, this property isn't bindable, the number of items is unknown in the view's codebehind constructor, there could be zero items, or over nine thousand. There's a possibility that our number of items instantly decreases to zero, or increases to n.

Things could and probably would go bad if there were n items, ViewCacheSize was set to 1..n, then the number of items decreased to 0 and SHCV got ItemsSource bound before a custom message event got raised and consumed in the view's codebehind, which is the only way I could communicate with it and set ViewCacheSize.

So... What to do with this and how to use it?

Oh boy ! this is an old undocumented settings created when the component was not even open source :)
But I just tested it in the sample app and it works (on android anyway).

GridPage.xaml

<sho:CollectionView
                        x:Name="HorizontalListView"
                        CollectionLayout="{Binding Mode, Converter={converters:ListModeToListLayout}}"
                        CollectionLayoutChanging="ListLayoutChanging"
                        CollectionPadding="10,30,10,75"
                        ColumnCount="2"
                        CurrentIndex="{Binding CurrentIndex}"
                        DragAndDropEndedCommand="{Binding OnDragEnded}"
                        DragAndDropStartedCommand="{Binding OnDragStarted}"
                        DragAndDropTrigger="Pan"
                        EnableDragAndDrop="{Binding Source={x:Reference DragAndDropSwitch}, Path=IsToggled}"
                        InfiniteListLoader="{Binding SillyPeoplePaginator}"
                        ItemHeight="120"
                        ItemWidth="120"
                        ItemsSource="{Binding SillyPeople}"
                        ScrollBeganCommand="{Binding OnScrollBeginCommand}"
                        ScrollEndedCommand="{Binding OnScrollEndCommand}"
                        TapCommand="{Binding TapCommand}"
                        ViewCacheSize="10">
                        <sho:CollectionView.ItemTemplate>
                            <DataTemplate>
                                <sho:DraggableViewCell x:Name="DraggableViewCell">
                                    <sho:Shadows
                                        x:Name="Shadow"
                                        CornerRadius="10"
                                        Shades="{StaticResource ThinDarkerNeumorphism}">
                                        <views:SillyGridCell
                                            Margin="16,13,16,13"
                                            BackgroundColor="{StaticResource DarkerSurface}"
                                            CornerRadius="10">
                                            <views:SillyGridCell.Triggers>
                                                <DataTrigger
                                                    Binding="{Binding Source={x:Reference DraggableViewCell}, Path=IsDragAndDropping}"
                                                    TargetType="views:SillyGridCell"
                                                    Value="True">
                                                    <Setter Property="BackgroundColor" Value="{StaticResource DarkSurface}" />
                                                </DataTrigger>
                                            </views:SillyGridCell.Triggers>
                                        </views:SillyGridCell>
                                    </sho:Shadows>
                                </sho:DraggableViewCell>
                            </DataTemplate>
                        </sho:CollectionView.ItemTemplate>
                    </sho:CollectionView>

Anyway I dunno if it will improve your scrolling issues anyway...
You can still try to inherit from CollectionViewRenderer in android and override OnElementChanged and if e.NewElement != null try to set different settings on the RecyclerView (the Control property).

like the setItemViewCacheSize method or the setHasFixedSize (see https://blog.mindorks.com/recyclerview-optimization)

Don't know how big your ItemsSource is, but can you quickly try setting the ViewCacheSize value to something larger than the collection size to see if it bombs out on you as well?

I don't know if it's worth investing my company's paid time into dealing with this, or just declaring "it jitters on first scroll" a feature and closing as "won't fix" :)

I just put 100 as a value for a 20 items collection...
If it is slow on the first scroll, it could also be caused by too many bindings.
In fact if your list is just a simple readonly list (like cards or something), you can try to use OneTime binding mode, it will drastically speed up the recycling of your views.
You can also use this compiled binding lib: https://github.com/levitali/CompiledBindings, who is way more fast than XF ones (see the videos here: levitali/CompiledBindings#4)

Also, have you tried in release without debugging ? sometimes, it could be a bit laggy on debug but smooth on release

OK, so I've been unable to make this work. It might be okay when started (if set in XAML; codebehind is a no-no), but as soon as the collection is rebound - poof, exceptions.

Doesn't matter, first scroll is jittery, that's not a bug, it's a feature to improve performance later :)

Binding and layout are quite complex (and yes, it's onetime), they're compiled (though not with the lib you mentioned) and we're at the limit of what is doable. The app is too complex for Xamarin in general, I think... Xamarin is too complex for Xamarin, in fact.

I tried SHCV also in a phone-specific layout, which is a story all by itself, and lost almost the entire last week trying to make that work. It's supposed to be in xct TabView with a couple of labels bound to some data. As soon as there's binding on those labels and SHCV is present, Xamarin's Droid renderer asplodes and takes upwards of 10 seconds to draw anything. Plus, scroll is then dead in SHCV :) No binding on labels - no problem. Your component has nothing to do with it... just another stupid Xamarin thing which makes absolutely no sense and cannot be fixed because I'm sure even the Xamarin people themselves wonder how anything works at all.

(will try 2.1 prerelease from #53 as soon as I can, sorry for the delay)

oh boy how I understand you !
I had so much of those weird rendering issues with xamarin.
The xamarin layout engine seems broken... one day I even created a layout called JppLayout which translates to "I can't take it anymore Layout" which await 200 ms after its first layout, then force a re-layout.

Droid and iOS are relatively decent. It's only when you venture into UWP territory (which we did) that you realize how fragile and buggy everything is. An example: if you have a password field that has text in it, select all of that, and then quickly type two letters in a row, the app crashes. Can't be fixed, it's a Xamarin bug because they just HAD to play smart and have an intermediate control that's not a native UWP wrapper.

Hopefully MAUI will make things better; Xamarin is just broken at its core and beyond salvation :)

won't be fixing this.