Infinite scrolling using MVVMCross and Xamarin.

A technical overview of how to achieve infinite scrolling using MVVMCross and Xamarin.

By Howard Bayliss19th August 2015

This post is a technical overview of how you can achieve Infinite Scrolling (also known as Endless Lists or Incremental Lists) using MVVMCross and Xamarin.

A fling in Cannes.

We used this approach for the Cannes Lions Festival kiosk app, shown in the picture above. One element of the App allows a user to scroll through a grid of festival entries by swiping right-to-left. There could be a large number of entries to show (100s, possibly 1000s – depending on filter selections). To load all of this data in one go would be slow, so instead the App was built to incrementally add more entries, as-and-when the user swipes.

The app was built specifically for Windows devices. However, I’ve now created cut-down sample versions of the App which demonstrates Infinite Scrolling on Android and iOS devices.

What is a "fling"?

Instead of slowly swiping through the entries, a user might choose to perform a Fling. This is a gesture whereby the user initiates a long, fast, swipe of the grid. This will cause many new entries to be added to the grid in rapid succession, with the user interface updating equally rapidly.

This is not a typical usage pattern. However, our original Cannes App was able to handle this, remaining responsive throughout (or “Fast and Fluid” as Microsoft advocates). The goal of the Xamarin Android and Xamarin iOS projects is to replicate this functionality and the following sections describe an approach for this.

Videos in the next section show the Android version in action.

Example videos

The 2 videos below show the Android version of the sample App, running on a Sony Xperia Z1 - I apologise for the basic styling of the App. The first shows the App running at full speed:

In this second video, I’ve deliberately added a random delay (of between 500 and 2500 milliseconds) to the image loading code. I’ve done this to simulate potential network latency, which might be the case if the images are being downloaded from the web. You will see that, despite the deliberate delay, the list scrolls as quickly as before:

Solution overview.

An overview of the solution is shown in the figure below:

There are 3 platform-specific projects, one for each of Windows, Android and iPhone. These projects contain “Views” which render a list of entries. In the case of Windows, a sideways scrolling GridView is used to render the entries’ list. For Android and iPhone, vertically scrolling views are used.

In addition, there are various “portable” or platform-independent projects. They are portable because they can be used in any of the platforms i.e. the code can be shared across the platforms. Of these, the MVVMCross Core Layer contains View Models. The Views are able to data-bind to properties on these View Models in order to retrieve and render the entry data.

The entry data is stored in a SQLite database where each entry consists of a thumbnail image, title, entry number, etc.  A SQLite query is executed in order to incrementally return a subset of the data, as the user swipes through the list. The data is returned from the database to the Core layer via Business Logic and Data Access Layers.

For this solution, there are some golden rules:

1. The platform-independent projects must only contain portable code.

2. The solution should maximise sharing of code via the View Models.

3. The implementation of the Infinite scrolling must be fast and fluid, necessitating the use of asynchronous code in order to prevent a poor user experience.

An example of [3] is asynchronous loading of thumbnail data, which is performed separately to other entry data loading and occurs on-demand e.g. only when a thumbnail is displayed in the grid. Further sections describe this in more detail.

Windows project.

Microsoft makes it easy to add Infinite Scrolling to a Windows App. They do this by providing a .Net Interface called ISupportIncrementalLoading, which is shown here:

In order to use this Interface, the process is:

  1. Create a new Collection class (which I named IncrementalCollection) which is sub-classed from ObservableCollection and which implements the ISupportIncrementalLoading Interface.
  2. In a View Model, create a property that returns an instance of IncrementalCollection, which is populated with data via the Business Logic Layer.
  3. Bind the property to a GridView (or ListView).

The GridView will detect that it is bound to a Collection that implements ISupportIncrementalLoading and will automatically call the method LoadMoreItemsAsync when the user has scrolled to the edge of the current data.

(In fact, the GridView will call this method even if the user has not reached the edge. It does this intelligently to provide a buffer of data which it may need to render in the future.)

You will also notice that LoadMoreItemsAsync is an asynchronous method. These features ensure that the GridView in the Cannes App is very responsive to Flings; it never becomes sluggish. However, there is a problem with this approach….

The IAsyncOperation Interface (shown above) is Windows-specific. Therefore it should not be used within the View Model – it would break the portability of the Core project.

The solution to this problem is to use a Factory class that can return a platform-dependent instance of an IncrementalCollection.

To illustrate this, see the figure below which shows the Entries property in the View Model:

In this example, when running the Windows App, a Windows-specific IncrementalCollectionFactory (which is defined within the Windows App project) is resolved by the MVVMCross Inversion of Control (IoC) container.

The Factory has a method called GetCollection that creates a Windows-specific IncrementalCollection (again defined within the Windows App project), passing into the constructor a data retrieval delegate (shown above). The delegate is called asynchronously, with parameters to support data paging.

When bound to a GridView (via the property), the instance of IncrementalCollection calls the data-loading delegate in its LoadMoreItemsAsync method.

In summary, the View Model property is able to return a platform-dependent IncrementalCollection, filling it with data using a method which is also defined within the View Model.

To maximise sharing of code, the goal of the Android and iOS projects is to reuse the above.

Data paging.

The query used to return entry data uses paging, which limits the amount of data returned and ensures that the query executes quickly. The query is executed with a couple of parameters - a start page (which is set to the number of items currently in the IncrementalCollection) and a page size (a property of the View Model). This creates a “sliding window” of entry data.

The query does execute quickly. However, while writing this blog post I came across an even more efficient approach to SQLite paging, which is described here.

Asynchronous images.

For each entry in the IncrementalCollection, the GridView renders a user interface template, as shown below:

Controls on the template are data-bound to properties on an EntryViewModel (which is the type of the objects stored in the IncrementalCollection). For example, the “Title” Label control is bound to the “Title” property of the EntryViewModel. This data was returned in the initial query of the database.

However, to maximise performance, each entry’s thumbnail is retrieved and rendered separately from other data and the whole process is asynchronous.

To achieve this, a XAML Image control is bound to a “Thumbnail” property on the EntryViewModel, which asynchronously loads the thumbnail data from the database as a byte array.

The Image control cannot bind to a byte array directly and so a Windows-specific Value Converter is used to convert the byte array into a Windows-specific BitmapImage. This conversion process is also implemented asynchronously.

Asynchronous image loading means that, when scrolling, new entries are added to the GridView as quickly as possible (maintaining the Fast and Fluent approach).

Android project.

Moving to the Android project, the goal was to reuse the Entries property and the asynchronous image loading approach used in the Windows project. This is facilitated by Android’s approach to ListViews and Adapters.

ListViews and Adapters.

Essentially, when an Android ListView control is data-bound to a Collection, an intermediary called an “Adapter” is used to load the data from the source into the ListView. Adapters are used for performance reasons, to facilitate the reuse of Android Views.

MVVMCross provides equivalents of the Adapter and ListView classes, namely MvxAdapter and MvxListView, which were used in the Android sample App. In addition I created Android-specific implementations of the ISupportIncrementalLoading interface, the IncrementalCollection class and IncrementalCollectionFactory class, the figure depicts this:

The logic to load more items can be contained within the Adapter, sub-classed from MvxAdapter (which I named IncrementalAdapter). The custom Adapter overrides 2 methods:

1. SetItemsSource – performs an initial load of data into the IncrementalCollection source list. Data-binding then causes the ListView to display the items.

2. GetView – this is called when an item is the ListView must be displayed. It is triggered when the user scrolls the list and so can be used to call the LoadMoreItemsAsync method of the source Collection when needed i.e. when the user has scrolled to the end (or need to the end) of the list.

Both methods (above) call another method (within the same class) called LoadMoreItemsAsync which is shown below.

This method checks to see if the Adapter’s source implements ICoreSupportIncrementalLoading (I named the Interface like this to avoid any confusion with Microsoft’s Interface). If it does support the Interface, the Interface’s LoadMoreItemsAsync method is called.

Asynchronous images.

Binding the ListView to the Entries property is simply done via the XML of an Android Layout, as shown below:

For each entry in the list, an MvxItemTemplate is shown (which is referenced above as “@layout/item_entries”). This is the template which displays the entry’s title, thumbnail, etc.

In order to bind the Thumbnail control (which is an Android ImageView control) to the Thumbnail property of an EntryViewModel, it is necessary to create an MVVMCross Custom Binding, sub-classed from MvxAndroidTargetBinding.

Within this Custom Binding, the target type is set to “byte[]” (meaning “byte array”). However, like the Windows Image control, the Android ImageView control cannot bind to a byte array (however it can bind to an Android Bitmap). It would be possible to implement an Android Value Converter to do the conversion. However, this is unnecessary as the conversion can be done within the Custom Binding itself, in the SetValueImpl method.

The conversion is done asynchronously using a BitmapFactory, which has a method to convert a byte array to a Bitmap.

By using this approach, the Android project can now bind to the Entries property and asynchronously download and convert thumbnail data.  This takes place on-demand i.e. there may be 100s of items in the ListView, but the thumbnail data is only loaded when the associated entry is scrolled into view.

ListView reuse.

As mentioned above, an Adapter will attempt to reuse Views (visible items) within the ListView. This, in conjunction with the asynchronous thumbnail loading, means that when scrolling through the list, a user may see a previously loaded image before the correct image is displayed.

(This may not be immediately apparent, but can be seen if a delay is deliberately added to the thumbnail loading code.)

To prevent this, the SetValueImpl method makes the Thumbnail ImageView control invisible until the thumbnail data has been downloaded and a Bitmap has been created and assigned to the control.

iOS project.

The approach for the iOS (iPhone project) was very similar to the Android Project, the comparison below shows how the Android approach can be mapped to the equivalent iOS approach:

Android approach.

Android ImageView control

MvxListView

MvxAdapter

MvxAndroidTargetBinding

iOS Approach approach.

iOS ImageView control. This is bound to the Thumbnail property.

TableView (the standard TableView class can be used).

MvxSimpleTableViewSource. Create asub-classand add a method calledCreateBinding. Use this method to create a binding toan MvxViewControlleras well as performing the initial data load. The logic used to load more items can be implemented by overriding theGetOrCreateCellFormethod.

MvxTargetBinding. Override theSetValuemethod in order to convert the thumbnail byte array to aUIImage.

You will notice that the table does not include mappings for the IncrementalCollection-Factory classes. The reason for this is that the implementation of these classes (it transpires) do not need to include any platform-specific code. Therefore they can both actually exist within the Core project and be shared between the Android and iOS projects.

(Despite this, the registration of IncrementalCollectionFactory with the MVVMCross IoC must still take place within the Android-and-iOS specific projects, rather than within the Core. If it is done within the Core, it will overwrite the registration performed in the Windows-specific project.)

Acknowledgements.

Thanks to following people, who have created frameworks and libraries used in this solution: Stuart Lodge et al (MVVMCross); Stephen Cleary (AsyncEx); Øystein Krog, Frank Krueger, Tim Heuer (SQLite.Net PCL)  and Jimmy Bogard (AutoMapper).