Pagination With Observablecollection with Xamarin on iOS and Android

Your users expect your apps to perform their best. Lists are an important part of any apps, however they are sometimes misused and the #1 source of lags in applications. As a reminder, any long running operation executed on the MainThread will block and will be perceived by your users as Freezing or lagging.

Both iOS and Android provide mechanisms to ensure the upmost performance of those UI. Android provides the RecyclerView and iOS provides a UITableViewSource. How to leverage those controls is extensively discussed on the web and is beyond the scope of this article.

Let’s say you need to handle very long lists (ex: hundreds of items). The first rule of thumb is that you do not want to load all the items at once. Even if done on a background thread, the user will perceive your application as slow since thousands of records need to be downloaded over a possibly poor network. Meet pagination.

If you have been using MVVM on .net, you must be familiar with ObservableCollection. This a collection class that dispatches INotifyPropertyChanged events in relation to Collections.

Let’s see how we can leverage ObservableCollection for pagination:

public class LazyObservableCollection : MvxObservableCollection<CellViewModelBase>, ILazyObservableCollection
    {
        //Delegate that returns the list of cells as well as the total number of items available on the API
        public delegate Task<Tuple<IEnumerable<MvxViewModel>, int>> PageLoaderDelegate(int currentPage);

        private readonly PageLoaderDelegate _pageLoader;
        private int _totalNumberOfItems;
        private int _currentIndex;
        private MvxNotifyTask _executingTask;

        public LazyObservableCollection(PageLoaderDelegate pageLoader, int pageSize) : base()
        {
            _pageLoader = pageLoader;
            _pageSize = pageSize;
            _currentIndex = 0;
        }

        public Task LoadMoreAsync()
        {
            if (_executingTask?.IsSuccessfullyCompleted ?? true) {
                int currentPage = _currentIndex / _pageSize;
                _executingTask = Task.Run(() => _pageLoader(currentPage).ContinueWith(t => {
                    var cells = t.Result.Item1;
                    var totalNumberOfResults = t.Result.Item2;
                    _totalNumberOfItems = totalNumberOfResults;
                    _currentIndex += cells.Count();
                    AddRange(cells);
                }, TaskScheduler.FromCurrentSynchronizationContext()));
            }

            return _executingTask;
        }

        public bool HasMoreItems => _currentIndex < _totalNumberOfItems;
    }

    public interface ILazyObservableCollection
    {
        Task LoadMoreAsync();
    }

The example above has been simplified for the purpose of this post. Some improvements include the ability to add a Loading cell when items are being loaded and Exception handling.

There are 3 important pieces in this snippet:

public delegate Task<Tuple<IEnumerable<MvxViewModel>, int>> PageLoaderDelegate(int currentPage);

This is the delegate that tells the observable collection how to get the next round of paginated view model that it needs to display.

LoadMoreAsync

LoadMoreAsync is where the magic happens. We check if there is a Loading task already running and if there isn’t we create a new Task to GET the next round of items. Finally we make sure to update the Collection from the UIThread to prevent updating the UI from a background thread.

ILazyObservableCollection

This is the interface that abstracts aways the functioning of the observable collection. on iOS you would call collection.LoadMoreAsync() from the UITableViewSource in GetOrCreateCellFor when you detect that the current cell index is at the end of what has already been loaded. On Android this would be invoked from onBindViewHolder.

Happy coding!