A Working Sample Application with Infinite Scrolling for Windows Phone 8

September 9th 2013 Windows Phone MVVM

Some time ago I published a blog post describing how to handle incremental loading in scrolling lists with large amount of data on Windows Phone 8. After receiving an email asking me for a working solution, I decided to publish it and make it available to everyone, instead of sending it by email. If you're only interested in the code, you can download it from the public repository I set up, but I suggest reading this accompanying post as well. It should make it easier to understand.

The best place to start looking at the code is RemoteService class. It simulates a proxy for retrieving batches of data from a remote server. There are two features worth pointing out:

  • The total number of items returned is passed to its constructor to demonstrate the behavior once all the items are loaded.
  • There is a Debug.WriteLine call in LoadItems method which prints out the call parameters to the output window making it easy to see when the method gets called in runtime.
public RemoteService(int totalCount)
{
    _totalCount = totalCount;
}

public IEnumerable<string> LoadItems(int startIndex, int count)
{
    Debug.WriteLine("Loading {0} items, starting at {1}", count, startIndex);
    int maxIndex = Math.Min(startIndex + count, _totalCount);
    for (int index = startIndex; index < maxIndex; index++)
    {
        yield return String.Format("Item {0}", index);
    }
}

ViewModel class uses RemoteService to load items on demand. It is important to load enough of them initially for the scrollbar to appear, otherwise new loads won't get triggered. All of the loading logic is contained inside an ICommand class:

  • Execute method loads the next batch of items each time it is called.
  • After receiving the result it checks whether the service returned less items than requested. In this case there are no more items available.
  • CanExecute method is used to disable the command when no more items are available, preventing further calls to Execute.
  • Items are stored in ObservableCollection<T> so that the UI is automatically notified when more items are added to the list
public ObservableCollection<string> Items { get; set; }
public ICommand LoadCommand { get; set; }

public ViewModel()
{
    Items = new ObservableCollection<string>();
    Items.AddRange(_remoteService.LoadItems(0, BatchSize));

    LoadCommand = new DelegateCommand(LoadMoreItems, MoreItemsAvailable);
}

private bool MoreItemsAvailable()
{
    return _moreItemsAvailable;
}

private void LoadMoreItems()
{
    var expectedCount = Items.Count + BatchSize;
    Items.AddRange(_remoteService.LoadItems(Items.Count, BatchSize));
    _moreItemsAvailable = expectedCount == Items.Count;
}

The view has ViewModel set as its DataContext. It uses a LongListSelector for displaying the items:

  • Items ObservableCollection is bound to its ItemsSource property.
  • LoadCommand is bound to IncrementalLoadingBehavior and automatically called by it based on user's scrolling.
<phone:PhoneApplicationPage.Resources>
    <infiniteScrolling:ViewModel x:Key="ViewModel" />
</phone:PhoneApplicationPage.Resources>

<phone:PhoneApplicationPage.DataContext>
    <Binding Source="{StaticResource ViewModel}" />
</phone:PhoneApplicationPage.DataContext>

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <phone:LongListSelector ItemsSource="{Binding Items}">
        <i:Interaction.Behaviors>
            <infiniteScrolling:IncrementalLoadingBehavior 
                LoadCommand="{Binding LoadCommand}" />
        </i:Interaction.Behaviors>
    </phone:LongListSelector>
</Grid>

To see how it works, compile and run the application and then open the Output window to monitor data loading. The first 3 calls should happen immediately because the control automatically loads more data than displayed to make the scrolling smoother. Additional calls should happen while scrolling down the list. Once all of the items are loaded, the command is not executed any more.

In production code actually calling an external service, you'll need to use asynchronous calls instead of synchronous ones, but this should be easy to achieve using the async await keywords. If you use Caliburn, use IncrementalLoadingTrigger instead of IncrementalLoadingBehavior as already suggested in the original post. In this case you won't even need a command, since you can call a public method on ViewModel directly.

Copyright
Creative Commons License