FlickR Searching with Silverlight & Rx

This one was just a side-product of something else that I was toying with but I’d made this little Silverlight screen that let me search FlickR;

image

which you can run by clicking on the image above if you like ( it’s not too polished ) but I used the Reactive Extensions (Rx) in a couple of places in the code and so I thought I’d just share those bits.

Firstly, there’s no “Go” button on that search term up above in the UI;

image

I had the application wait until you’d typed at least a few characters and then give you a little time to make sure you’d finished typing and then it goes off and does a search based on what you typed. I did that in my view model and I did it with Rx;

      ObservableEx
        .FromPropertyChange(this, "SearchText")
        .Select(p => this.SearchText)
        .Where(text => text.Length > Constants.MinSearchStringLength)        
        .Throttle(TimeSpan.FromMilliseconds(Constants.SearchTextTimeoutMs))
        .ObserveOnDispatcher()
        .Subscribe(OnNewSearch);

this is a slightly weird bit of code. Firstly, ObservableEx.FromPropertyChange is just a little wrapper around Observable.FromEventPattern that I wrote;

    public static IObservable<object> FromPropertyChange(
      this INotifyPropertyChanged propertyChange, string propertyName)
    {
      return (
        Observable
        .FromEventPattern<PropertyChangedEventArgs>(propertyChange, "PropertyChanged")
        .Where(p => p.EventArgs.PropertyName == propertyName)
        .Select(p => p.Sender));
    }

Secondly, because INotifyPropertyChanged doesn’t actually pass you the changed value, you can see I’m making a call back in the previous snippet to Select( this.SearchText ). That is – I’m subscribing to a property changed event on a property on my own class but then I have to hit the property accessor again to get the value. That feels a bit weird and perhaps I should have passed a lambda into my FromPropertyChange wrapper function to grab the value from this.SearchText rather than do this?

Largely, that use of Rx just gives me the Throttle capability so that I can ignore the user’s typing up to a point but then take notice of it if they seem to pause for a little bit of time.

Another place where I used Rx was in calling out to the FlickR search REST API, essentially using the bridge to the Asynchronous Programming Model in order to make it easier for me to call WebRequest.BeginGetResponse and EndGetResponse.

I ended up writing a tiny helper method;

  internal static class ObservableWebRequest
  {
    public static IObservable<WebResponse> CreateDeferred(string uri)
    {
      HttpWebRequest request = WebRequest.CreateHttp(uri);

      Func<IObservable<WebResponse>> factory =
        Observable.FromAsyncPattern<WebResponse>(
          request.BeginGetResponse, request.EndGetResponse);

      return (Observable.Defer(factory));
    }
  }

and then chaining a whole bunch of my own bits onto it to gather a page worth of response data from FlickR;

        // Create a timer that will produce a value every so often (250ms)
        IObservable<long> slowDownTimer = 
          Observable
            .Interval(TimeSpan.FromMilliseconds(Constants.PopulateListBoxDelayMs));

        this.currentSearch =
          ObservableWebRequest       
            // Performs the async HTTP get when subscribed to.
            .CreateDeferred(uri.Uri) 
            // Takes the XML produced and parses it into a results page object
            .Select(wr => FlickrPhotoResultsPage.FromWebResponse(wr))
            // Gets back to the UI thread
            .ObserveOnDispatcher()
            // Updates some state based on the parsed FlickR results
            .Do(UpdateResultsPageDetails)
            // Takes the single pages of results and extracts all the parsed
            // photo details
            .SelectMany(resultsPage => resultsPage.Photos)
            // Slows the process down so it only produces a result every 250ms
            .Zip(slowDownTimer, (result, interval) => result)
            // Gets back to the UI thread
            .ObserveOnDispatcher()
            // Listens for success, failure, cancellation
            .Subscribe(
              AddImage, EndSearch, () => EndSearch(null));

That’s quite a chunk of code so I sprinkled some oddly placed comments into it to try and illustrate the chain that’s built up – there’s a bunch of my own functions in there so it’s not perhaps so obvious what’s going on without delving into the code on that particular snippet.

That IObservable ultimately produces the details (i.e. titles, URLs) of the page of images that I need to download and I feed them into a piece of code that downloads them asynchronously. I could have done this by chaining more work onto the previous observable but I wanted to separate that work out. Here are some images mid-load;

image

and, again, I just used Rx to handle that for me;

      ObservableWebRequest
        .CreateDeferred(this.Uri.ToString())
        .Select(response => response.ReadResponseToMemoryStream())
        .ObserveOnDispatcher()
        .Subscribe(
          SetImage,
          ex => this.InternalVisualState = ImageViewModelState.Errored,
          () => this.InternalVisualState = ImageViewModelState.Cancelled);

using that CreateDeferred helper method again to make me an IObservable<WebResponse> and then another little extension to read the response stream back and copy it into a separate memory stream.

As I say, this was just a bit of fun but one thing I noticed when I came to drop the app on the website was that it was a little on the large side at around 1.5MB.

That was a bit of a shock so I took out one of the fonts I’d embedded and it dropped down to 600KB still with one font embedded into my assembly.

I had a quick look what was inside the XAP;

image

and so my assembly with the embedded font ( ChunkFive Roman from here ) and FlickR logo is coming in at 84KB so that’s not too bad but I picked up a bunch of dependencies;

  • 45KB for System.Xml.Linq because I used LINQ to XML to parse my XML.
  • 50KB for Microsoft.Expression.Drawing.dll because I made use of Triangle rather than a simple Path.
  • 76KB for Microsoft.Expression.Effects because I made use of one slide effect on a state transition change
  • 90KB for System.Windows.Controls because I made use of ChildWindow.
  • 104KB for System.Windows.Controls.Toolkit because I made use of WrapPanel.
  • 143KB for Rx itself.

So I could easily shave off another 250KB here and perhaps another 100KB if I could live without the WrapPanel.

Here’s the source-code for download if you want to play around with it a little.