You have probably come across the term “Reactive Programming” more than once at this point; it has been gaining a lot of popularity over the last few years and I’ve been wanting to talk about Reactive Programming and the options on how to use it in Android for a couple of weeks now. I decided to make this a two-part series; in this first part, we’ll go over the concept of Reactive Programming without going into too many details about the different implementations out there (the idea is to understand some key concepts), and, in the second part, we will talk about the different libraries available for Android developers.
Before jumping in a new programming paradigm we need to understand the one you (probably) already use, that is Imperative Programming (IP for short), in IP the programs consist of a sequence of statements that include commands to modify the state of the application. Let’s take a look at one simple snippet:
Just some assignments and function calls, really simple. What is truly important to highlight (in this case) is that, in an imperative setting, it doesn’t matter that we have expressed the variable
bin terms of
a, the value of
b won’t change if we update
a, so we say that the change on
a is not propagated.
This is probably how you are accustomed to working as a lot of mainstream languages are imperative by nature, with an increasing number of languages supporting functional programming constructs.
Reactive Programming (RP for short) refers to a specific declarative paradigm designed to deal with asynchronous data streams, their changes over time and the propagation of these changes. In RP we observe events that have been modeled as one or more streams of data, with a (potentially infinite) flow of values moving in the stream.
You can think about these streams as pipes where values flow as they are emitted by the producer with you at the end of the pipe receiving these values. One important aspect of the streams is they can issue three different types of values, which require different types of reaction, in general, they can issue:
- A value signal when a new value has been emitted into the stream.
- An exception signal when some error has occurred.
- A completion signal when no more elements will be put into the stream.
It’s important to note that anything can be modeled as streams; from the data produced by device sensors, responses from network calls and even changes in variable’s values.
We observe some data flow using a subscriber, that is the code to execute whenever new values have been put in the stream. Some readings call these Observers, some call them Subscribers. Moving forward, we will use these terms interchangeably.
Of course, sometimes you want to manipulate or transform these streams in some way before you can actually use them in your app, while we do not directly modify the stream but rather create a new one applying operators to the original stream. Libraries implementing RP usually provide a set of standard operators that you can use to transform the stream in an elegant and declarative way to get the values you need flowing down, one of these operators is
filter, and as you can imagine it's used to pass only the items you are interested in the stream based on some criteria.
And we got a lot of useful operators we can use, for instance, let’s say that you need to count the number of red circles in the stream, we can use the
In the upcoming article, we will see in more detail how to use these operators and others in RxJava and Flow implementations.
Cold vs Hot Flow
The flow of data can be hot or cold, and this is important because it affects how you should threaten the changes you get in the flow. Hot streams are active, independently of whether there are observers that can respond to their changes or not, so you get only the changes that have occurred after you started observing them.
Cold flows, on the other hand, are lazy, they only emit values when there is an observer subscribed that can react to them, and the values are not shared among observers, so each of them gets their flow of data.
This is a term borrowed from fluid mechanics, and it is “a resistance or force opposing the desired flow of f̵l̵u̵i̵d̵ ̵t̵h̵r̵o̵u̵g̵h̵ ̵p̵i̵p̵e̵s̵ data”. It is the scenario you get when the observers of a particular stream cannot keep up with the rate at which the values are being emitted, this can lead to nasty application crashes which is no good, so this is something that RP libraries need to deal with one way or the other and in general, there are three main strategies:
- Buffering, saving the ‘excess’ of emissions and send them down the stream once resources have become available.
- Dropping, basically discarding those emissions that exceed our capacity.
- Control the producer, giving control to the consumer about how much data should be sent down the stream.
Of course, the right strategy to use will depend on the specific scenario you, as a developer, are facing. It could be acceptable to drop some data between updates of a GPS based application, but it’s completely unacceptable to do it when writing a file from the internet to disk, in this case, a buffering strategy would make more sense.
Now you have taken a sneak peek into what Reactive Programming is and how it compares with the Imperative Paradigm. In our next article, we will see how to use it to build an application, how the different implementations available work so that you can decide which one is best suited for your needs. See you next time!