RxSwift Made Easy: Part 3 - Transforming Observable Sequences
Welcome to the third instalment in this RxSwift series. In the last article, we took a look at the different kinds of Subjects with working examples to see how they handle replaying and stop events. By now, creating and subscribing to sequences should seem like a fairly simple task, so let’s now dive into another level of complexity and start transforming our sequences.
What Does It Mean To Transform A Sequence?
Often you will subscribe to a sequence only to receive an element type that is different from the one you want to work with. For example, you may want to work with a String representation of a Date, so rather than receive elements of type Date in your next events, you will want to transform your sequence so that it sends you a String type. Sounds familiar? We already deal with these kinds of transformations in standard collection types using methods like map, flatMap, reduce, and filter. We will see similar names, but as reactive programming is an asynchronous paradigm the behaviours of reactive operators can have a few surprises.
In this article we will look at map, flatMap and flatMapLatest with an examination of their marble diagrams and a coding example for each.
Let’s dive in!
Map And An Introduction to Marbles
Map is a nice, simple starting place as its behaviour is virtually the same as for collections. It goes through each element, applying a transformation and returns a new sequence with the modified elements. As we would expect, the original sequence remains unchanged.
So far we have been able to describe reactive operators without the assistance of visual diagrams, but now we are starting to get into territory where they can be really useful, so without delay let me introduce you to “marbles”. You can see the name of the operator and the applied transformation in the central rectangle. The horizontal lines indicate the changes in the sequences over time. In this case we have an input sequence above the operator and an output sequence below. For the map operator, each time the input sequence emits a next event, the element is transformed and the new value is emitted to the output sequence.
Here is what it looks like in code:
In this example we can see that we create a sequence with element type Int (line 3) and map it so that the value of the elements is squared (line 5). The most important thing to notice is that the original sequence remains unchanged, and a new sequence is output which means we now have two sequences (seqOrig and seqMapped). We can then subscribe to both of those sequences (lines 7 and 12). As next events are emitted in seqOrig (lines 17–19) they are transformed and emitted to the mapped sequence. As we would expect, both subscriptions are disposed by the dispose bag as the scope is exited.
FlatMap
We have seen how easy it is to apply transformations to elements in a sequence, but what if we want to access an observable property in the element, or what if the element is itself an observable? For this, we need to use flapMap.
In this flatMap marble diagram, notice that the first element O1 in the input sequence is transformed and the result is emitted to the output sequence. But interestingly, when that same element emits a next event some time later , that is also transformed and emitted to output sequence. In other words, the output sequence is subscribed to the observable elements and will receive the next events in those elements whenever they occur. This asynchronicity is very different from what we would find in a traditional flatMap.
Once again here it is in action:
In this example, we want to observe the food items that a group of people eat. To begin we create an entity called Person, which is notable for its property “thingsEaten” (ex 1, line 4). As you will remember, a publish subject does not replay any historical information so it is simply going to emit a next event when a person eats something. To assist with this posting, we can create a convenience method eat(item: String) (ex 1, line 10)
With the entity set up, we can create two people: Matt and Shawna (ex 2, lines 1–2), and an observable of type person, so that we can subscribe to the eating habits of the person element (ex 2, line 6). At this point we use flatMap so that we can access the observable .thingsEaten in our person element. You may also recall asObservable() is required to access the BehaviorSubject inside our Variable wrapper (ex 2, line 8).
The Variable has a buffer of only one element and we have initialized it with our Matt object. Not surprisingly, when Matt consumes some food, the message he emits is received by our flatMappedPeople and printed to the console (ex 2, line 22). Shawna also eats an apple (ex 2, line 24), but as she has not been added to the observedPerson nothing is printed.
And here’s where things get interesting… When we assign Shawna to the value of our observedPerson (ex 2, line 20), our Matt object is no longer stored in observedPerson. It would be a reasonable assumption that Matt’s food consumption will no longer be mapped. Right? Nope. Remember that when an element goes through flatMap, the output sequence has subscribed to the observable element in the input sequence. So even if the input sequence no longer holds reference to that observable, the output sequence will. In our example, the observedPerson changes from observing Matt to observing Shawna, which means the flatMappedPeople sequence will now transform the next events being sent from both Matt and Shawna. This is evidenced when our two people consume another item each (ex 2, line 28 and 30).
FlatMapLatest
Our last example showed us a great way to cumulatively observe the events emitted in an observable element. However, what if we only want to observe one element at a time? We would need to unsubscribe from the previous one before adding the new one. Thankfully, there’s a much smarter approach and that’s the last operator we will examine today: FlatMapLatest.
From the marble diagram we can see once again that we can reach into the first observable element O1 of our input sequence and perform a transformation before sending the result in a next event to our output sequence. However, unlike flatMap, once a new element O2 has been added to the input sequence, subsequent next events emitted by O1 are no longer emitted to the output sequence. Only the latest element will have it’s events emitted.
As you will see, if we simply substitute flatMapLatest into our previous example, our results are quite different.
In this example, you can see the use of flatMapLatest (line 7). This time, when the Variable value changes to Shawna (line 25), she becomes the element whose next events will be transformed. While Matt’s next events will be received by the flatMapLatest operator, the transformations of those events will never be sent to the output sequence flatMappedPeople. This can be observed as Shawna’s two eaten items are printed (line 27 and 31) but Matt’s is not (line 29).
Wrap Up
In today’s article we looked at transforming sequences and in doing so, we learned how to not only transform elements, but also transform observables in our elements. There are of course, many more reactive operators within this topic, and I hope that you will take some time to explore them. If there was a big take-away that I hoped you would get, it is the idea that you can understand a lot about new operators by studying their marble diagrams. These are widely used in Reactive documentation, and there are even cool interactive diagrams that you can play with.
In our next instalment, we will take a look at filtering sequences, so please keep your ear to the ground!