Sunday, April 15, 2012

Using the Cocoon Data Framework

Over the last series of posts I have discussed the Cocoon data framework. This aims to simplify the retrieval and display of data sets within Windows 8 Metro style applications. In particular it assists with pulling data from remote systems via typical page-based web APIs and displaying them as continuous lists.
The posts so far have covered,

The Cocoon Sample Application

Included with the Cocoon source code (available from Codeplex) is a sample application that demonstrates there principles. This application is a simple Flickr viewer that displays the current list of “interesting” photos according to the Flickr API. Note that to compile this application you will need to register for a Flickr API key here and place this in the ‘FLICKR_API_KEY’ constant within the ‘Data/FlickrApi.cs’ file.
In this application the FlickrApi class provides basic access to the Flickr API with a single method GetInterestingPhotos() [Note: This is for demonstration purposes only and is not designed to demonstrate good practice for creating a web API client]

namespace Cocoon.Sample.Data
{
    public partial class FlickrApi
    {
        // *** IMPORTANT : YOU MUST ENTER YOUR API KEY BELOW! ***
        public const string FLICKR_API_KEY = "ENTER YOUR FLICKR API KEY HERE";
 
        public async Task<FlickrPhotoPage> GetInterestingPhotos(int page, int perPage)
        {
            ...
        }
    }
}

You will notice that this retrieves data in a paged manner, passing in the page to return as an argument and returning a FlickrPhotoPage as a result. Note also that since this is an asynchronous method call the result is returned as a Task. In the UI however we have a single scrollable list of photos, so we will use the Cocoon data framework to link these together.

Creating a New DataListSource

It is recommended that when you are using the Cocoon data framework you expose the IDataListSource implementations though a data source singleton class. Since data list sources can perform some caching of the data this allows several pages of your application to use the same underlying source. In the sample application this it the FlickrDataSource class.

namespace Cocoon.Sample.Data
{
    public class FlickrDataSource
    {
        // *** Fields ***
 
        private FlickrApi flickrApi = new FlickrApi();
        private IList<FlickrPhoto> interestingPhotos;
 
        // *** Methods ***
 
        public IList<FlickrPhoto> GetInterestingPhotos()
        {
            // Create a single instance of the interesting photos list
            // This can then be shared between multiple view models
 
            if (interestingPhotos == null)
            {
                InterestingPhotosDataListSource dataListSource = new InterestingPhotosDataListSource(flickrApi);
                interestingPhotos = (IList<FlickrPhoto>)new VirtualizingDataList<FlickrPhoto>(dataListSource);
            }
 
            return interestingPhotos;
        }
    }
}

Here we simply create an instance of our FlickrApi class along with a lazily generated InterestingPhotosDataListSource object. Whilst you can expose this to your rest of your application as is, here we also create a VirtualizingDataList<FlickrPhoto> passing the data list source into the constructor. Since this implements IList<FlickrPhoto> we can data bind to this in our UI.
The most interesting part of this is the InterestingPhotosDataListSource class – this is the class that you will need to write for each of the lists of data you wish to expose through the Cocoon data framework. As we have already discussed the Flickr API exposes the interesting photos feed as a series of pages, therefore we will derive from PagedDataListSource<FlickrPhoto>.
The three methods that we need to implement are FetchCountAsync, FetchPageSizeAsync and FetchPageAsync. Often however, as in this case, you will find that the number of items and page size are returned as part of the API call for returning the first page. Hence we only need to write an implementation of FetchPageAsync and use this to write the other two methods.


namespace Cocoon.Sample.Data
{
    public class InterestingPhotosDataListSource : PagedDataListSource<FlickrPhoto>
    {
        // *** Fields ***
 
        private FlickrApi flickrApi;
 
        // *** Constructors ***
 
        public InterestingPhotosDataListSource(FlickrApi flickrApi)
        {
            this.flickrApi = flickrApi;
        }
 
        // *** Overriden Base Methods ***
 
        protected override Task<DataListPageResult<FlickrPhoto>> FetchCountAsync()
        {
            return FetchPageAsync(1);
        }
 
        protected override Task<DataListPageResult<FlickrPhoto>> FetchPageSizeAsync()
        {
            return FetchPageAsync(1);
        }
 
        protected async override Task<DataListPageResult<FlickrPhoto>> FetchPageAsync(int pageNumber)
        {
            FlickrPhotoPage flickrPage = await flickrApi.GetInterestingPhotos(pageNumber, 20);
            return new DataListPageResult<FlickrPhoto>(flickrPage.Total, flickrPage.PerPage, flickrPage.Page, flickrPage.Photos);
        }
    }
}


In the FetchPageAsync(…) method we directly call the FlickrAPI.GetInterestingPhotos method (using the new C# async support). The result is then returned as a DataListPageResut<T> with the total number of items, the number of items per page, the page number returned, and a list of the items within that page.

Data Binding to the Data List

Since in this sample application we are using the MVVM pattern the user interface is split into two parts, the ‘InterestingPhotosPage.xaml’ and the ‘InterestingPhotosViewModel.cs’. In the view model we simple call FlickrDataSource.GetInterestingPhotos method that we wrote earlier and expose this IList<FlickrPhoto> as a property of the view model.

this.InterestingPhotos = flickrDataSource.GetInterestingPhotos();


And in the XAML we bind to this property as you would any other collection,


<CollectionViewSource
            x:Name="itemsViewSource"
            Source="{Binding InterestingPhotos}"/>

Summary

I hope that I have shown above a brief insight into the power of the Cocoon data framework. We started by writing a simple “data list source” implementation describing how to access data from a paged web UI. The framework then allows you to easily expose this as a list that can be data bound to a UI, with support for virtualization and ISupportPlaceholder for free.

2 comments:

Sla said...

Great set of posts so far. I noticed that when I run the flickr sample and click about halfway down the scrollbar it queries flickr for every page up to that point one after the other. Is there a way to only query page 20 (and 19, 21 maybe...)? It also seems to cause the scrollbar to stop working correctly since you can't scroll left very well after that. Is that a known issue?

Unknown said...

slacroix,

Yes, you are correct that the current behaviour is to retrieve all pages as you scroll quickly to an item deep in the list. In fact this is related to how the GridView requests the items. Regarding the issue with scrolling left - this is definitely a bug.

The good news is that I am currently in the process of re-architecting the internals of this part of the Cocoon data framework. One of the design aims is to allow rapid scrolling to only retrieve the page displayed rather than all preceding pages.

As they say - watch this space!

Thanks for the useful feedback,
Andy