Nested Collection Performance in Xamarin Forms
I came across a discussion on the Xamarin Forms slack chat on the subject of nested collections. This is of particular interest to me as several of the apps we’ve built utilises nested collections in some form.
I suggested that, as we have done in several apps, the BindableLayout view would be used to turn any StackLayout into a data bound collection. Christoph Huber pointed out that the performance for this approach would not be great compared to a ListView.
I set about testing this to see how much worse BindableLayout compared to the ListView and to the soon to be released CollectionView.
The code for the project can be found here.
I created a test app that uses TheMovieDb to list trending movies and display their posters.
Communication to the API was provided via the excellent TMDbLib C# wrapper.
Before navigating to the relevant page, the movie information is downloaded from the API. The first 5 pages of 20 of this week’s trending movies are requested from the API.
Then, the movie posters are pre-loaded into the FFImageLoading‘s cache with the correct dimensions requested. This ensures that there is no downloading needed when navigating to the page.
To check the performance of the page, a stopwatch is started ad passed into the view model.
When the page calls OnAppearing, the stopwatch is stopped. This is not ideal as we can’t be sure that this is when the page has fully rendered, but without page life-cycle hooks for rendering, this is the best we have currently.
Pages were created to test the performance of different approaches.
- CollectionView (Preview)
- BindabeLayout using StackLayout
- BindableLayout using FlexLayout
A viewmodel was created to hold the data for the movies. We’re using our own NuGet package F3N.YaMVVM for a lightweight MVVM framework.
The data is passed through the constructor of the viewmodel and the viewmodel is bound to the page’s BindingContext using YaMVVM.
Single Collection Results
All tests were performed on a Samsung Galaxy S8 running in debug mode.
Test 1: Render time
The first test was a render time test, performed by loading a fresh instance of the app each time and then navigating to the page and timing how long it took to show the results.
Both the ListView and CollectionView rendered almost indentically with ListView rendering in 269 milliseconds and the CollectionView at 266 milliseconds.
The BindableLayout pages, as advised rendered a lot slower. StackLayout rendered in 2691 ms and FlexLayout in 2829 ms.
Test 2: Reload render time
The second test consisted of revisiting a page from the main menu after it has been loaded initially. The CollectionView (45 ms) consistently performed better than the ListView (57ms), but the BindableLayout of StackLayout and FlexLayout pages still suffered with results of 2038 ms 1934ms respectively.
Test 3: Scroll performance
The third test was scroll performance.
The ListView page performed very well, only pausing slightly as items came into view and FFImageLoading retrieved the image from memory to display it. The CollectionView performed even better with hardly any noticable pauses as images were loaded from cache.
The best performance for scrolling however, were the BindableLayout pages. As they load all items when the page initially loads, there’s no rendering to do as items come into view. This results in a buttery smooth scrolling experience. I could not differentiate between the performance of the StackLayout or the FlexLayout.
To create a nested collection, a page was created to list the movie production companies with IDs from 1 to 20 and their first 20 movies as returned by the API.
The companies were added to a ListView with a nested Horizontal StackLayout with BindableLayout attribute. Movie poster images were preloaded, but the company logos were not. This had some impact on the rendering of the posters.
The movie posters and truncated titles only were shown due to space restrictions.
For this page, the loading time is appears reasonable at 197ms, but doesn’t reflect reality.
Initially, horizontal scrolling is fine, with smooth scrolling along the movies. But that changes when scrolling vertically.
As rows appear you can see the rendering really starts to stutter.
Initially I thought the images must be downloading again but after enabling FFImageLoading VerboseLogging I can see that it is loading them from cache.
The performance is not suitable for a real world app.
The same layout was created by replacing the ListView with a StackLayout with a BindableLayout property. Loading and rendering time were high again, but the performance was much improved.
For instantly loading pages with a single collection, there’s no substitute for ListView / CollectionView. However, if you can let the user know that you’re loading the screen with visual feedback after they tap a button, then for best scroll performance (assuming 100 or less rows), then BindableLayouts produce fantastic results.
When using nested collections you have to be more careful and spend time optimising your XAML. It’s best to keep it as flat as possible – limit the number of nested elements.
For nested layouts it looks like there’s no silver bullet, but BindableLayouts perform best when scrolling.