A Working Sample Application with Infinite Scrolling for Windows Phone 8
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 inLoadItems
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 toExecute
.- 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 itsItemsSource
property.LoadCommand
is bound toIncrementalLoadingBehavior
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.