Incremental Loading in Xamarin.Forms CollectionView: A Maintenance Task Guide
Learn how to add safe pagination to a legacy Xamarin.Forms CollectionView using RemainingItemsThreshold, with reentrancy guards and version pinning for apps awaiting .NET MAUI migration.
08 Dec 2025, 12:11 UTC

The problem: long lists freeze legacy Xamarin apps
If you maintain a Xamarin.Forms application that must keep running until a .NET MAUI migration is complete, you have likely hit the point where a single screen tries to bind hundreds or thousands of items to a ListView. The result is sluggish scrolling, high memory use, and occasional UI freezes while the data source materializes. The useful takeaway is that CollectionView replaces ListView and supports incremental loading through RemainingItemsThreshold, but the event fires repeatedly while the user scrolls near the end, so the load-more handler needs a reentrancy guard.
Desired outcome
Append pages of data to a CollectionView as the user approaches the end of the list, without issuing duplicate requests or blocking the UI thread. The implementation should use the MVVM pattern that Xamarin.Forms supports natively, keep UI logic out of code-behind, and remain compatible with a future move to .NET MAUI.
Prerequisites and version assumptions
- Xamarin.Forms 4.0 or later.
CollectionViewand its threshold API arrived in 4.0. Because Xamarin.Forms is frozen at version 5.0 and behavior changed between 4.x and 5.0, pin thePackageReferenceto an exact version and record it. Do not assume an API newer than 5.0 exists. - A paged data source. Your service or repository must accept a page number or offset and return a bounded page of items.
- MVVM with compiled bindings. Views derive from
ContentPage, view models implementINotifyPropertyChanged(an interface that notifies the UI when a property changes), and commands use the built-inCommandclass. Addx:DataTypeto your page so binding paths are validated at build time instead of failing silently at runtime. - End-of-support context. Xamarin reached end of support on May 1, 2024. Treat this work as maintenance for a legacy app, not a foundation for new features.
Focused procedure
1. Define the view model
Create a view model that exposes an ObservableCollection for the bound items, a page counter, and a boolean guard that prevents overlapping load operations. Implement INotifyPropertyChanged so the UI updates when properties change.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
public class PagedListViewModel : INotifyPropertyChanged
{
private readonly IItemService _itemService;
private int _currentPage;
private bool _isLoading;
public ObservableCollection<ItemViewModel> Items { get; }
= new ObservableCollection<ItemViewModel>();
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
public PagedListViewModel(IItemService itemService)
{
_itemService = itemService;
}
public async Task LoadNextPageAsync()
{
if (IsLoading)
return;
IsLoading = true;
try
{
var nextPage = await _itemService.GetItemsAsync(_currentPage + 1, PageSize);
if (nextPage.Count > 0)
{
_currentPage++;
foreach (var item in nextPage)
Items.Add(item);
}
}
finally
{
IsLoading = false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return true;
}
}
The IsLoading guard is the critical piece. RemainingItemsThresholdReached can fire many times while the user scrolls through the threshold zone; without the guard you will issue redundant network or database calls.
2. Configure the CollectionView in XAML
Set RemainingItemsThreshold to a small integer—typically 3 to 10—so the next page begins loading before the user reaches the absolute end. Bind ItemsSource to the view model collection and wire the threshold event to a code-behind handler that delegates to the view model.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.PagedListPage"
x:DataType="local:PagedListViewModel">
<CollectionView ItemsSource="{Binding Items}"
RemainingItemsThreshold="5"
RemainingItemsThresholdReached="OnRemainingItemsThresholdReached">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:ItemViewModel">
<Label Text="{Binding Title}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
Because x:DataType is set, a misspelled property such as Titel will fail the build rather than producing a blank label at runtime.
3. Bridge the event to the view model
In the code-behind, keep the handler thin. Cast the BindingContext to your view model and invoke the load method. Do not place business logic here.
private async void OnRemainingItemsThresholdReached(object sender, EventArgs e)
{
if (BindingContext is PagedListViewModel vm)
{
await vm.LoadNextPageAsync();
}
}
Run this handler on the UI thread. Xamarin.Forms raises the event on the main thread, but if you offload work to a background scheduler inside the view model, marshal collection mutations back to the UI thread before calling Items.Add.
Expected checks
| Check | How to verify |
|---|---|
| Compiled bindings catch errors | Introduce a typo in a binding path and confirm the build fails. |
| One load per page | Add a debug log in LoadNextPageAsync; scroll to the end and confirm a single entry per page, not one per scroll frame. |
| No duplicate items | After loading three pages, verify the item count equals page 1 + page 2 + page 3 counts. |
| UI remains responsive | Scroll while a page loads; the list should not stutter or block input. |
Relevant recovery options
- Repeated network calls. If the debug log shows overlapping requests, confirm the
IsLoadingguard is set before the firstawaitand reset in afinallyblock. - Threshold never fires. Verify
RemainingItemsThresholdis greater than zero and that theCollectionViewhas enough items to scroll. If the first page is smaller than the threshold, the event may fire immediately; treat that as a valid signal to load page two. - Version drift. If behavior differs across devices or CI builds, compare your
PackageReferenceagainst the Xamarin.Forms 5.0 release notes and pin the exact version. Xamarin.Forms is frozen at 5.0, so there is no newer stable package to move to. - Migration risk. If you are planning a .NET MAUI move, note that
CollectionViewcarries forward, but audit any custom renderers or effects because those do not always map one-to-one.
Limitations
Xamarin support ended on May 1, 2024, and Xamarin.Forms is frozen at version 5.0. This pattern is a maintenance technique for an existing app, not a reason to start new Xamarin development. Additionally, RemainingItemsThresholdReached is a coarse signal: it tells you the user is near the end, but it does not tell you how many items remain. If your backend requires exact end-of-list detection, return a total count from the service and compare it to Items.Count.
Practical verification
Attach the debugger to a device or emulator, scroll to the bottom of the list, and watch your logging output. You should see exactly one LoadNextPageAsync invocation per page. Then temporarily set RemainingItemsThreshold to a very large number, rebuild, and confirm the event fires immediately after the first page binds. Finally, revert the threshold to your chosen value and confirm normal incremental behavior resumes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.