At 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.
It can be a bit intimidating when you start with RX. So I 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 allows you to do this in 1 stream of data/events. Let’s start with a fake search function to return some data.
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.
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>
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.
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.
Update! In my initial post I used SelectMany to get to the actual data. Then I got this feedback from the Rx creators themselves.
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