Part of the power of RX lies in it’s ability to compose complex operations and keep the resulting code maintainable. I previously showed how to perform Drag-Drop operations with RX. This time, I want to take a look at a slightly more complex operation: Detecting “Shake” gestures on the Windows Phone 7.
The phone includes the ability to detect motion in 3D space through the built-in Accelerometer in the Microsoft.Devices.Sensors library. This sensor raises events when the phone is moved with information about how forcefully it was moved in the EventArgs. Detecting shakes is more complex than just knowing if the device was moved. We need to make sure that the user’s motion was aggressive enough to warrant a shake detection.
In addition, we need to know if the user moved the phone aggressively enough multiple times within a small enough time span. Simply monitoring the ReadingChanged event doesn’t fill the needs of detecting a real “Shake”. To manage all of these state changes and the times that each change occurs with traditional imperative code, we would either need to set up a number of queues remembering each motion that exceeds the tolerance and the times each happens and then act upon them when a sufficient number of these movements happen within a given time threshold. GoogleBinging this finds a number of sample implementations including Joel Johnson’s article and the recently released Shake Gesture Library. Both of these versions work with traditional events and manage the state internally.
If we use RX, we can simplify the code a bit by taking advantage of Observable.FromEvent to create an observable collection from the Accelerometer.ReadingChanged event, and the TimeInterval method to track the amount of time that passes between each accelerometer reading that exceeds the given tolerance (MinimumOffset).
Imports System.Linq Imports Microsoft.Devices.Sensors Imports Microsoft.Phone.Reactive Public Module ShakeObserver Const MinimumOffset = 1.44 Const TimeThreshold = 200 Public Function GetObserver(ByVal accel As Accelerometer) As IObservable(Of IEvent(Of AccelerometerReadingEventArgs)) Dim readingChangedObservable = Observable.FromEvent(Of AccelerometerReadingEventArgs)(accel, "ReadingChanged") Dim query = From knocks In (From startEvent In readingChangedObservable Where (startEvent.EventArgs.X ^ 2 + startEvent.EventArgs.Y ^ 2) > MinimumOffset). TimeInterval Where knocks.Interval.TotalMilliseconds < TimeThreshold Select knocks.Value Return query End Function End Module
We can then consume this ShakeObserver in client code as we would any other Observable collection.
Dim accel As New Accelerometer accel.Start Dim query = From shake in GetObserver(accel) Select shake query.Subscribe(Sub(_) DoSomething())
Of course, if we are composing even more complex interactions, the power of using Observables here would be even greater as that’s where RX truly shines.