Use Reactive Extensions to throttle TextChanged events and subscribe to search results

Download the demo project RxSearch

rx

Are you new to Reactive Extensions and looking for a quick tutorial on how to perform a few common tasks in a Windows Store App? In this tutorial you will learn how to throttle TextBox.TextChanged events and use them to search in a collection. You will see how you subscribe to the search results and inform your UI of changes.

Why am I writing about Rx?

wunderAt 6Wunderkinder we make Wunderlist, a multi-platform, multi-user, online/offline, asynchronous application that allows you to organize your life and business.

The multi-user/asynchronous nature of the application presents us with a lot of challenges. We need a local repository (model) that holds the data we need when we create ViewModels that support the Views. But what happens if another user edits data you are looking at right now? A clever sync mechanism changes the model and these changes need to be broadcast to the viewmodels. For this we use Reactive Extensions.

When my colleagues Chris and Martin showed me Rx for the first time, I had no idea what I was looking at. So we started with a very simple example that uses Rx to listen to TextChanged events, throttles them and uses them to perform a search. Then, instead of having a function returning a list of search results, the search function returns an IObservable to which the caller can subscribe. The UI looks like this:

rx

How would you do this without Rx?

  • Add a KeyDown event handler to the TextBox
  • Use a DispatcherTimer to collect a few keystrokes before searching
  • Call a search function and return the result
  • Update the ListBox

Rx allows you to do this in 1 stream of data/events. Let’s start with a fake search function to return some data.

Use Rx to perform the search

  • Start a blank Windows 8.1 app
  • Use Nuget to add Reactive Extensions – Main Library
  • Use Nuget to add Reactive Extensions – XAML Support Library
  • Create class Api.cs and add the following code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;

namespace RxSearch
{
    public class Api
    {
        public IObservable<List<string>> Search(string filter)
        {
            var list = new List<string> { "Vera", "Chuck", "Dave", "John", "Paul", "Ringo", "George" };
            var filteredList = list.Where(x => x.ToLower().Contains(filter.ToLower()));
            return Observable.Return(filteredList.ToList());
        }
    }
}

The Search function does not return a List<string> but an IObservable<List<string>>. This changes a pull-based result to a push-based result. To return a value, we use Observable.Return(list) which returns an IObservable<List<string>> and completes the stream.

Creat the UI in MainPage.xaml:

<Grid Background="#FF96B209">
  <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
  <TextBox x:Name="textbox" Width="400" Margin="0,0,0,20" FontSize="20"/>
  <ListBox x:Name="listbox" Width="400" Height="400" FontSize="20" />
  </StackPanel>
</Grid>

To use the search you edit MainPage.xaml.cs:

using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using Windows.UI.Xaml.Controls;

namespace RxSearch
{
    public sealed partial class MainPage
    {
        public MainPage()
        {
            InitializeComponent();

            var api = new Api();

            var textchanges = Observable.FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
                h => textbox.TextChanged += h,
                h => textbox.TextChanged -= h
                ).Select(x => ((TextBox)x.Sender).Text);

            textchanges
                .Throttle(TimeSpan.FromMilliseconds(300)) // result on threadpool 
                .Select(api.Search)
                .Switch()
                .ObserveOnDispatcher() // send back to dispatcher
                .Subscribe(OnSearchResult);

            api.Search("").Subscribe(OnSearchResult);
        }

        private void OnSearchResult(List<string> list)
        {
            listbox.ItemsSource = list;
        }
    }
}

I split the stream into two parts. The event from the TextBox and the data from the Search result.

TextChanges:

var textchanges = Observable.FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
                h => textbox.TextChanged += h,
                h => textbox.TextChanged -= h
                ).Select(x => ((TextBox)x.Sender).Text);

textchanges is now an IObservable<string> that is…well…observable. To observe it, we subscribe to it.

Subscribe to IObservable

Update!

In my initial post I used SelectMany to get to the actual data. Then I got this feedback fromĀ  the Rx creators themselves.

switch

So I tried Select().Switch() and it works beautifully!

textchanges
 .Throttle(TimeSpan.FromMilliseconds(300)) // result on threadpool 
 .Select(api.Search)
 .Switch()
 .ObserveOnDispatcher() // send back to dispatcher
 .Subscribe(OnSearchResult);

And that’s really it. OnSearchResult is just a handler to actually fill the ListBox:

 private void OnSearchResult(List<string> list)
        {
            listbox.ItemsSource = list;
        }

Download the demo project RxSearch