Windows Phone – Reactive Extensions (Rx)

In my previous post I talked about how important it is to use page transitions and animations in your application to increase the overall user experience. Wouldn’t it be nice if we could extend this experience a bit, and take it to the next level of user feedback and interaction.

Fortunately there is a framework called Reactive Extensions (Rx) that enables us to do all kinds of super cool stuff in our applications.

This post will show how easy it is to use the Reactive Extensions framework to increase the user experience when loading data from services and showing it to the user in a more elegant way.

Reactive Extensions (Rx)

First of all, Reactive Extensions is a library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators. You can start learning about Rx at MSDN if this is new to you: http://msdn.microsoft.com/en-us/data/gg577609

In the sample project that I’m going to build, I will simulate a call to a web service to fetch data, and when data is returned I will populate a ListBox with the data. The items will be added one by one to the list with a nice subtle animations to give feedback to the user that items are being added to the list. I will simulate fetching from different services and add them result items to the list as soon as they arrive.

1. Create the Phone project

You start by creating a Windows Phone 7.1 project using the “Windows Phone Databound Application” template.

This will give you all the needed UI automatically, we will just refactor it and change so that it uses loading animations and Reactive Extensions when displaying the items to the user.

2. Change the UI

Now let’s change the Item Template for the ListBox so that each row has a solid background color (will be easier to see the animation) and finally add an animation/storyboard that is executed when a row is loaded (using event trigger).

The final ItemTemplate:

    <DataTemplate>
        <Grid x:Name="TileGrid">
            <Grid.Triggers>
                <EventTrigger RoutedEvent="Grid.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation 
                                Storyboard.TargetName="TileGrid"
                                Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                                Duration="0:0:0.5" To="0" />
                            <DoubleAnimation
                                Storyboard.TargetName="TileGrid"
                                Storyboard.TargetProperty="Opacity"
                                Duration="0:0:0.5" To="1" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Grid.Triggers>
            <StackPanel Margin="0,0,0,17" Width="432" Height="78" Background="{StaticResource PhoneAccentBrush}">
                <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
            </StackPanel>
            <Grid.Projection>
                <PlaneProjection CenterOfRotationX="0" CenterOfRotationY="0"
                        RotationY="-90" />
            </Grid.Projection>
        </Grid>
    </DataTemplate>

If we run the application now, we already see an animation for the loaded event.

But all items are loaded at the same time. What if we wanted each row to be loaded one by one with a 200 millisecond delay? How would we do that?
One option is to use a background worker that has a 200 millisecond sleep in each loop that adds an item to the Items collection that is used as ItemsSource on the ListBox.
Let’s try that and see how it would look like and then later simplify the source code using Reactive Extensions.

3. Add item loaded delays
Start by refactoring the MainViewModel.cs LoadData() method to hold the sample data in a temporary list (so that we can use that list in our background worker to finally add them one by one to the Items collection)
Hint: to change the source code on multiple rows at the same time, you can hold down the Alt key while highlighting text in the editor to select parts of the text on multiple rows and then type on your keyboard to change the text on multiple rows at the same time. Realy nice feature :)

The final source code with BackgroundWorker:

    public void LoadData()
    {
        // Temporary hold the items in memory
        ObservableCollection<ItemViewModel> items = new ObservableCollection<ItemViewModel>();

        // Sample data; replace with real data
        items.Add(new ItemViewModel() { LineOne = "runtime one", ... });
        items.Add(new ItemViewModel() { LineOne = "runtime two", ... });
        items.Add(new ItemViewModel() { LineOne = "runtime three", ... });
        ...
        items.Add(new ItemViewModel() { LineOne = "runtime sixteen", ... });

        this.IsDataLoaded = true;

        // Create background worker that adds all items to the Items collection with delays
        var worker = new BackgroundWorker();
        worker.DoWork += delegate(object sender, DoWorkEventArgs args)
        {
            // Loop through all the items
            for (int i = 0; i < items.Count - 1; i++)
            {
                // Create a slight delay and add each item to the Items collection
                Thread.Sleep(200);
                Deployment.Current.Dispatcher.BeginInvoke(
                    () =>
                    {
                        Items.Add(items[i]);
                    });
            }
        };
        worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs args)
        {
        };
        worker.RunWorkerAsync();
    }

If we run the application now we have a nice animation for each row when they are added to the Items collection

4. Add Reactive Extensions (Rx)
So the above use of background worker is working just fine, but can we simplify the source code using Rx?
First of all to use Rx you need to add the following references to the project

  • Microsoft.Phone.Reactive
  • System.Observable

and the following using statements

    using Microsoft.Phone.Reactive;
    using System.Linq;

Then finally remove the Background Worker code section and replace with the following code and also add the method AddItem

    public void LoadData()
    {
    
    ....

    // Convert the result to an Observable sequence
    items.ToObservable()
        // Add selector method that selects each item with 200 ms delay
        .Zip(Observable.Interval(TimeSpan.FromMilliseconds(200)), (d, t) => d)
        // asyncrounously notify observers using the current dispatcher
        .ObserveOnDispatcher()
        // subscribe a value handler to an observable sequence
        .Subscribe(AddItem); 
    }

    private void AddItem(ItemViewModel item)
    {
        Items.Add(item);
    }

If we run the application now we have the same result as expected. Although the source code is now a lot nicer, and we don’t have to worry about cross thread issues, index out of bound when looping through the items etc. This is just a really simple useage of Rx framework, but there is a lot more cool stuff you can do with it. Perhaps I’ll write a post later on a more complicated scenario.

Performance

You should always pay attention to performance on the phone when using animations and transitions. If we enable draw regions we can see that during loading the list box items are redrawn several times, although this is OK in this simple application but something to pay attention to.

    // Show the areas of the app that are being redrawn in each frame.
    Application.Current.Host.Settings.EnableRedrawRegions = true;

Happy Coding!

Advertisements
    • m.savazzi
    • January 29th, 2012

    Question on Rx extension:

    Background:
    1) I developed the app to be fully based on binding
    2) I have a Extender View bound to a data class where the expanded field is a description, thus the length can change a lot
    3) the list, loaded in one shot from XML via deserialize, has more than 100 elements

    The issue is that when I enter the page the phone hangs for several seconds while trying to build the page.

    Will the Rx approach make the page load more responsive?

    Any suggestion to avoid 5 sec od “dead black” screen?

    I’m now trying to change the list with a longlistselector to gain some advantage with the virtualized data pages

    • Harry
    • May 4th, 2012

    Do you have any solution for the same animation with the ‘unloaded’-event?
    The 2 problems which came across were:
    1) The control is already gone when this event is triggered
    2) The observable should start with the lowest element and work its way up, how could that be performed.

    I was just asking, because I thought you would try too, to make the same ‘move away animation’ as seen in the OS.

    • Harry, you can catch the navigate back event and cancel that and call your “unloading page animation”, then when animation is done, navigate back manually. Just make sure you do not do this for the main page, that is, when the user tries to exit the application since you cannot manually exit an application you cannot stop or manipulate the navigate back when the user tries to exit your app.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: