UWP project with MVVM: Using Rx
Intro
My team and I are currently working on a UWP project which is a complete rewrite of a WinRT app that we have been building.Recently we started development on the 'Search' functionality. It's that typical search paradigm: only hit the backend when the user has at least typed 3 characters and when he hasn't typed anything in the searchbox for 500ms. And, ofcourse, cancel an ongoing search operation when the user changes his search query.
It's the same functionality that we built before in our (almost legacy) WinRT app. Back in days we used CancellationTokens and Task.Delay to do the job. And it worked well! But, I didn't like the code to be honest, passing that cancellationToken around, cancelling it when needed, checking if it hadn't been cancelled yet...
https://pieeatingninjasblog.files.wordpress.com/2015/11/ugly-code1.jpg?w=660 Part of the legacy code, passing around a CancellationToken
Note that this or any code sample in the post, isn't really our 'production' code of our project. I've mimicked it so I could easily share it. Also, the code shown here might not be production-ready yet, it might need some additional tweaking before using it in a production environment.
Using Rx
This time around I said to my team to take a look at* Reactive Extensions* (Rx) in order see if we could implement this search thing in a cleaner way. So we did and the initial result looked somewhat like this (code-behind of the view):
https://pieeatingninjasblog.files.wordpress.com/2015/11/initial-rx.jpg?w=660 Doing all the RX stuff in code-behind
I'll explain, very high level, what this code does, I'm not going to elaborate too much on Rx itself (see links at the bottom of this post for more information about Rx).
First, we create an IObservable sequence of the TextChanged event of our SearchBox. This means, each time this particular event is triggered, the sequence will push the data of this event. The data that will get pushed to us is of type EventPattern that holds the same information that you would get when handling the event: a sender (object) and eventArgs (TextChangedEventArgs in this case).
Next, as we are only interested in the Text that has been entered in the TextBox and not in the eventArgs or anything else, we cast the sender to a TextBox and select the Text property. The Throttle operator handles our 'timeout' of 500ms: this means that the IObservable sequence only pushes an item after 500ms, instead of 'realtime'.
So what this code does in human language: when the text of the TextBox changes, the IObservable sequence pushes us the entered text after 500ms. If the text changes within these 500ms, only this new value will get pushed (after 500ms), the previous item won't.
It's exactly on that moment when we get the entered text from our IObservable sequence, that we want to launch our search request to the server. And that is what we do in the following code:
https://pieeatingninjasblog.files.wordpress.com/2015/11/result-qry.jpg Launching Search call for the entered query
We also added an additional method to our SearchDataService, one that accepts a query string and returns an IObservable<IEnumerable<Result>>
https://pieeatingninjasblog.files.wordpress.com/2015/11/search-observable.jpg?w=660 Search call that returns the results as an IObservable
So when an item is pushed in the throttledTextChangedSequence, we do a call to our SearchDataService and return the result as an IObservable.
Finally, we are going to subscribe to our result sequence so that we can define what code needs to execute whenever a search query is executed. In this scenario, we want to update the Results property of our ViewModel with the result of our search. From each result, we are only interested in the thumb_url.
https://pieeatingninjasblog.files.wordpress.com/2015/11/subscribe11.jpg?w=660 Selecting the thumb_url and update the Results property.
And that's it! Bye bye CancellationTokens... But is this code really more elegant?
Let's try to make this code more elegant!
Well, while the above code works, and it's already a bit cleaner than working with CancellationTokens, it is far from being elegant. This block of code in code-behind just feels wrong in an MVVM architecture (although I believe that, in some scenarios you sometimes better 'break the architectural rules' rather writing complex workarounds). There has got to be a cleaner way to do this.
So I came up with the following: move this code to the ViewModel and instead of converting the TextChanged event of my TextBox to an IObservable sequence, I'm going to convert the PropertyChanged event of my QueryText property:
https://pieeatingninjasblog.files.wordpress.com/2015/11/rx-vm-1.jpg?w=660 Rx on our ViewModel
This already felt a bit better... Notice though, that I have to do a bit more work here because I have to filter out the event coming from the property we are actually interested in by using an additional Where statement: Where(a => a.EventArgs.PropertyName == nameof(EnteredSearchQuery)).
But, there is still a lot of ceremony right? A lot of which should be repeated if we would like to convert the PropertyChanged event of an other property to an IObservable sequence.
Let's make this a little bit more generic, by creating an Extension method on the INotifyPropertyChanged interface as almost all of your ViewModels will implement this. This method should accept one parameter, a parameter that reflects the property we are interested in. We can make the type of this parameter an Expression<Func<T>> (the same type of parameter as used in the MVVM Light's RaisePropertyChanged method). This way we can pass in all the information we need from the property with only one parameter.
In this method, we'll just create an IObservable from an event, just like we did previously. This time, the target parameter is going to be the class that implements INotifiyPropertyChanged on which we call this method. The event we want to convert to an IObservable is the PropertyChanged event. For this we can use the nameof expression so we avoid typos and don't need to use 'magic strings'. We'll also need to use the Where clause we had before in order to filter out the event coming from the property we are interested in. The name of the property can also be retrieved via our parameter. The last thing we want to do is returning the actual value of our property. All put together:
https://pieeatingninjasblog.files.wordpress.com/2015/11/rxextensionmethod1.jpg?w=660 Generic GetPropertyAsObservable ExtensionMethod
Usage:
https://pieeatingninjasblog.files.wordpress.com/2015/11/usage.jpg?w=660 Using the GetPropertyAsObservable Extension Method
https://pieeatingninjasblog.files.wordpress.com/2015/11/final-result3.jpg?w=660 The final result
Conclusion
In this blogpost I showed you how you can use Reactive Extensions in a particular scenario. The main purpose was to look if we could get our 'legacy' code a bit cleaner without the usage of CancellationTokens. I worked out a way to integrate this nicely in an MVVM architecture, without needing to use an entire 3rd party toolset or framework (other than Rx itself of course).
I can think of at least one scenario in our App where the usage of Rx might result in some cleaner code. One of these days I'm going to work that out, and will probably put the result on my blog.
References:
- Example Source Code: OneDrive
- The Rx Team's blog: http://blogs.msdn.com/b/rxteam/
- Beginner's guide to Rx: https://msdn.microsoft.com/en-us/data/gg577611.aspx
Credits
Originally posted on : PieEatingNinjas Blog