🕓 7 MIN As 2024 wraps up, …
No matter the purpose of your application or website, performance is essential to a great user experience. Slow loading apps and webpages will turn users away almost immediately. Fortunately, Lazy Loading is a technique used to optimize load time by only loading required content at first, and loading any remaining page content once the user needs it.
In this article, Johnny guides us through the steps required to implement the Lazy Loading functionality for ListView and Gridview controls in Uno Platform WASM applications.
Lazy Loading or incremental loading is a strategy to delay the loading of objects until they are actually needed. This can help reducing resources used by the application and increase loading times for the item collection, because only parts of the data are loaded initially.
For my usecase the trigger for loading more items is a certain scrolling threshold in the ListView
and GridView
control. To get the current scroll position we need to somehow access the ScrollView
within the ListViewBase
which is the base class for ListView
and GridView
. We will use the VisualTreeHelper
class to get information about nodes in the visual tree and get a reference to the inner ScrollView
of the ListViewBase
var myScrollView = ExtendedVisualTreeHelper.GetFirstDescendant(myGridView);
myScrollView.ViewChanged += async (s, e) =>
{
if (myGridView.ItemsSource is not ISupportIncrementalLoading source) return;
if (myGridView.Items.Count > 0 && !source.HasMoreItems) return;
if (GetIsIncrementallyLoading(myGridView)) return;
if (((myScrollView.ExtentHeight - myScrollView.VerticalOffset) / myScrollView.ViewportHeight) - 1.0 <= loadingThreshold)
{
SetIsIncrementallyLoading(myGridView, true);
await source.LoadMoreItemsAsync(1);
}
}
We will add code to check for the need for lazy-loading whenever the ViewChanged
event is fired – this happens when the user scrolls the collection.
In this code, we use the ExtentHeight
, VerticalOffset
and ViewportHeight
of the ScrollView
to check if more than half of the scroll area was already scrolled, which will initiate the loading of more items.
Now we have the trigger when we want to load more items into our collection. For the actual fetch of the data we will use a special collection as the ItemsSource
of your ListViewBase
control.
public class PaginatedCollection : ObservableCollection, ISupportIncrementalLoading
{
public delegate Task Fetch(int start, int count);
private readonly Fetch _fetch;
private int _start, _pageSize;
public PaginatedCollection(Fetch fetch, int pageSize)
{
_fetch = fetch;
_start = 0;
_pageSize = pageSize;
}
public IAsyncOperation LoadMoreItemsAsync(uint count)
{
return Task.Run(async () =>
{
var items = await _fetch(_start, _pageSize);
await CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
foreach (var item in items)
{
Add(item);
if (Count > _pageSize)
{
//hack to give the UI time for layout udpates
await Task.Delay(20);
}
}
});
_start += items.Length;
return new LoadMoreItemsResult() { Count = (uint)items.Length };
}).AsAsyncOperation();
}
public bool HasMoreItems => true;
}
The PaginatedCollection
derives from ObservableCollection
which already has mechanisms for UI updates via databinding if the collection changes. I have added a Fetch
object to the class which can be any method responsible for getting more data (i.e. a call to a backend service). The fetch is called in LoadMoreItemsAsync
which itself is triggered if the scrolling threshold is reached (see extension classes above). The collection has a _pageSize
field which defines the number of items to be loaded.
The last part for using lazy-loading is to set the ItemsSource
of the control to a PaginatedCollection
and add the AddIncrementallyLoadingSupport="True"
attribute to the control.
local:ListViewExtensions.AddLazyLoadingSupport="True"
Let’s create an Items collection and set the page size accordingly. For the demo app i’m simulating a data fetch by adding a Task.Delay
to the method.
void LoadGrid()
{
Items = new PaginatedCollection- (
async (start, size) =>
{
var response = await FetchItems(start, size);
return response;
},
pageSize: 25
);
}
async Task
- FetchItems(int start, int size)
{
//simulates some work to get new items
await Task.Delay(200);
return _allItems.Skip(start).Take(size).ToArray();
}
I have tested the lazy-loading solution with ListView
and GridView
controls and in both cases the loading performance and responsiveness of the collection could be increased significantly. I also found out that ListView
seems to be optimized already and didn’t benefit as much as GridView
. I’m comparing the lazy-loading approach to controls with standard ObservableCollection
where all items are loaded initally. The Item
model is just a record with a name and a random image url.
public record Item(string Name, string Image);
These values are displayed in a StackPanel
within the DataTemplate
of the ItemsControl
. For testing i can set the count of the collection and trigger loading of each ItemsControl
separately.
You can see in the recording that the regular GridView
even blocks the UI when the loading starts. Note that this sample is running in InterpretedAndAOT
mode – more info below.
This post is just about lazy-loading for ListView
and GridView
– but there is more you can do to improve the performance of Uno WebAssembly apps. First of all, it makes a big difference what’s the runtime execution mode of your application. It can be set via the WasmShellMonoRuntimeExecutionMode
element in the .csproj file of the WebAssembly project. There are three different values for the element.
Interpreter
InterpreterAndAOT
FullAOT
Interpreter
is the default value and has the fastest build times but the slowest performance, since all the code is interpreted during runtime. For better perfomance you want to try some form of AOT compilation for your project. You could go with FullAOT
to compile all assemblies to wasm binaries Ahead of Time. This has the best performance in regards to code execution but produces the largest application package. For my usecase the InterpreterAndAOT
mode is a nice compromise, it allows mix AOT compilation and interpreted code.
Most of the tips and tricks for ListViewBase
are explained in the |documentation. Depending on your usecase it might help to experiment with List
vs. ObservableCollection
as the type of the ItemsSource
. It’s also worth noting that you can set your custom styles for the ItemTemplate
of the ItemsControl
. Especially when using GridView
, replacing the default GridViewItem
style can increase performance a lot, this is also mentioned in the Uno Platform documentation.
If you are new to Uno Platform, the best way to get started is to follow our official getting started guide. (5 min to complete)
For better discoverability and ease of adopting Uno Platform we have brushed up and published various working examples for Uno Platform, ranging from small single-feature samples to larger showcase applications. Check it all out and expect many more to come in the future.
Tags:
🕓 2 MIN Need a last-minute holiday …
Uno Platform
360 rue Saint-Jacques, suite G101,
Montréal, Québec, Canada
H2Y 1P5
USA/CANADA toll free: +1-877-237-0471
International: +1-514-312-6958
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.
Uno Platform 5.2 LIVE Webinar – Today at 3 PM EST – Watch