ReactiveX - Is it worth it?

ReactiveX

Hi there. I’m Dave. An extremely experienced (old..) Scottish developer at Adapptor in Perth, Western Australia, who’s been there done that and stumbled into mobile development a few years back after years in the web wilderness.

I’ve primarily been an iOS dev for the last few years with a smattering of Android but more recently and by nature of the way our team works, which is that everyone is expected to be cross skilled and work on any project, that’s become more blurred and mixed with a smattering of other languages.

But enough about me, we’re here to talk about ReactiveX.

When I joined the Adapptor team one of the first new things I was exposed to was ReactiveX, which the team had started to use in many of their projects across a variety of platforms.

So, what is it? And why do we need it? Let’s have a look at the second part of that first.

Web apps and mobile apps are by nature event based programs. A user taps on a button, moves a mouse, swipes a screen and the app reacts to that input.

These actions and the update of the view all happen on the main UI thread. If the user performs an action which requires the app to say download an image, post to an API or play a sound then we don’t want the UI thread to be blocked while that long running action takes place or the whole world would grind to a halt.

Asynchronous

When we need to perform a task which won’t hold up the main UI thread, we kick off a background thread which goes off and does its owns thing and then in some manner says to the main program, ok, I’ve got something to tell you, here’s a progress update, or here’s some data all while allowing the UI thread to proceed unhindered.

Delegates

In the first instance this was handled with delegate callbacks whereby the background task would have a delegate interface which it would expect any calling class to implement. A reference to the calling class would be passed into and held by the background task which could then call back to any of the delegate’s interface methods.

Delegates still have their place and can be very useful for things such as managing state where we have a complicated relationship between two objects which needs to be clearly defined however where we have an ad-hoc relationship where we are simply saying, go get me something and tell me when your done they aren’t really the answer.

Blocks

So we moved on to blocks and closures.

Blocks allowed us to centralise the code, improving readability and making the code more maintainable. Libraries such as AFNetworking cropped up which was and for many still is the de facto iOS networking library. Using this network GET request looks a little like this.

class Get {
    let manager = AFHTTPSessionManager ()
    // Get first response
    manager.get (
        "https://jsonplaceholder.typicode.com/todos/1",
        parameters : nil,
        progress : {
            (progress)
            in // Update Progress
        },
        success : {
            (task, response)
            in // Do something meaningful with the response
            print (response.debugDescription)
        },
        failure : {
            (task, error)
            in // Do something meaningful with the error
            print (error.localizedDescription)
        }
    )
    let dataFetcher = DataFetcher (delegate:self)
    dataFetcher.requestData ()
}
view rawget.swift hosted with ❤ by GitHub

Quite readable I’m sure you’ll agree. It’s pretty obvious where we handle progress updates and where we deal with a success or failure.

However theres still a fair amount of boilerplate code and one of the biggest issues with this kind of code is that when we get into recursive or repeated calls it gets hugely complicated dealing with the responses and the error handling. Also what happens if the object you are calling wants to return more than one piece of data, say a response every second for an indefinite period? We simply can’t handle every use case with this pattern.

So along came ReactiveX.

Observers and Observables

ReactiveX is an extension of the Observer Pattern.

Observable any object which acts as a data stream. It emits data in some kind of manner whether that’s once or over a defined period. The producer.

Observer a consumer which watches the observable and consumes the data emitted. The observer can then handle that data allowing it to say update the UI with a progress indication, display new data or throw an error, all without the observable caring what’s happened to its data. They are decoupled.

Schedulers the third part to the ReactiveX puzzle are the schedulers which allow you to control the async aspect of your calls, telling the observables and observers which thread they should run on.

Let’s look at the same example above written using RxAlamoFire an Rx compatible version of AlamoFire.

class Get {
   let sessionManager = SessionManager.default
        _ = sessionManager.rx.request(.get, "https://jsonplaceholder.typicode.com/todos/1", parameters: nil, encoding: URLEncoding.default, headers: nil)
            .json()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { (response) in
                    print(response)
            }, onError: { (error) in
                print(error.localizedDescription)
            }, onCompleted: {
                
            })
}
view rawget.swift hosted with ❤ by GitHub

As you can see there isn’t a huge difference in the number of lines in the code, however where there is a big difference is in the level of control and the extendability of the code.

The first few lines are pretty standard for any HTTP request, we create a manager, we define the URI of the request, the HTTPMethod, encoding etc. Nothing new here.

The differences are in the latter part of the command where we can see an observeOn parameter and three blocks wrapped in a subscribe parameter.

observeOn

No messing around creating threads or managing them, we simply tell the Observer where to run, in this case the MainScheduler. You can also use scheduleOn which tells the observable where to run.

subscribe

This is creating the observer which will watch the HTTP request.

onNext

This is called every time the Observable (the HTTP request) emits something. As this is a single GET request it’ll only be called once in this instance. This is where we handle the data returned.

onError

Pretty self explanatory, if an error occurs this is triggered and allows us to handle the error.

onComplete

This is called once the Observable has emitted everything it’s ever going to emit and can be used to tidy things.

If we don’t need the onComplete or onError we can just drop them from the call thereby simplifying the code.

Show me the true power of ReactiveX

The real strengths of ReactiveX are demonstrated when you start to chain things together and also in the fact that ANYTHING can become an observable. You can take some legacy non RX code and wrap it up in an observable, thereby allowing the code to be used in an RX sequence with new code.

This allows us to chain observables together, each one returning its result to the next observable.

We can filter, we can buffer, combine, process, merge all the responses into one, zip the responses of dozens of calls together in sequence. ReactiveX has dozens of operators to allow you to manipulate your observables in pretty much any way imaginable.

It’s also available for a whole host of languages, Java, Swift, Groovy, Javascript, Go, .Net, pretty much every major language has some kind of support.

The community is extensive and many large firms are using it.

That sounds great, what’s the catch?

Part of it is a mindset change for someone who is more used to the old ways of doing things. Understanding the basic pattern is pretty simple but it can get very complicated very quickly and although the observable sequences can look very neat and pretty they can be quite difficult to follow if you don’t know your flatMaps from your zips and your merges from your maps.

One of the biggest issues I had and continue to do so is the documentation. There are currently about 70 different operators to choose from and this can be extremely intimidating to a developer new to the framework in choosing which is appropriate. Not every operator is available for every language and in many cases the command and/or the syntax are completely different.

This would make it tricky enough to get used to but in many cases you find the command you think you need, find the language variation you want, hit the link expecting to get some documentation only to find TBD which is not exactly helpful!

So is RX for everyone? Well, possibly is the real answer I’m afraid! If you stick to the basic operators and use them selectively it can be extremely useful but mastering it and making full use of the myriad of options can at times be far more time consuming than the payoff in the end. I’ve had several times where I’ve been trying to force the use of RX when in fact using more standard code would have been much more simple and taken far less time (due to familiarity and documentation). Alternatively if you’re the kind who’s prepared to delve into the many options and experiment you can achieve some pretty incredible results and do some extremely powerful data transformations with very few lines of code.

I’ve certainly not covered everything here and there’s way more to cover which I might well pick up at a later date. Look forward to hearing others experiences and learnings from using ReactiveX.

Dave Cumming