Reaktive — a multiplatform library for reactive Kotlin

Arkadii Ivanov
Bumble Tech
Published in
5 min readMay 10, 2019

--

Reactive programming is really popular today not least because it has loads of plus points: the absence of what is called ‘callback hell’; a built-in mechanism for processing errors; and a functional programming style that reduces the likelihood of bugs occurring. It makes it much easier to write multi-threaded code as well as easier to manage (combine, split and transform) data streams.

Lots of programming languages have their own reactive library: RxJava for JVM, RxJS for JavaScript, RxSwift for iOS, Rx.NET and so on.

But what is there for Kotlin? Logically, it would be RxKotlin. And this library does in fact exist. However, it is little more than a set of extensions for RxJava2, i.e. what you might call ‘sweeteners’.

Ideally, however, we’d prefer a solution that meet the following criteria:

  • Multiplatform — in order to be able to write multiplatform libraries using reactive programming and spread their use throughout the company
  • Null safety — Kotlin’s type system protects us from ‘billion-dollar mistakes’, so null values should be permissible (for example, Observable<String?>)
  • Covariance and contravariance is another very useful hallmark of Kotlin, which enables us, for example, to cast Observable<String> type safely to Observable<CharSequence>.

At Badoo we decided not to sit around hoping for all this but went ahead and made this library ourselves. As you will already have guessed, we called it Reaktive and you can find it on GitHub.

In this article we’re going to take a closer look at the expectations vis-à-vis reactive programming in Kotlin and to what extent these are met by what Reaktive has to offer.

Three natural advantages of Reaktive

Multiplatform

Its first natural advantage is the most important. Currently, our iOS, Android and Mobile Web teams all work independently of one another. They are subject to the same requirements and the design is the same, but each team does its work on its own.

Kotlin allows you to write multiplatform code, but you have to forget about reactive programming. Yet we want to be able to write shared libraries while still using reactive programming and to spread their use within the company or upload them to GitHub. Potentially, this approach could significantly reduce development time as well as the overall amount of code generated.

Null safety

This mainly concerns a drawback with Java and RxJava2. In short, null cannot be used. Let’s try and get to the bottom of the reason for this. Take a look at this Java interface:

Can the result be null? In order to eliminate ambiguity, you are forbidden from using null in RxJava2. And if you need to use it anyway, the options ‘Maybe’ and ‘Optional’ are available. But there are no such problems with Kotlin. You could say that Single<User> and Single<User?> are different types, and that any problems will already become apparent at the compilation stage.

Covariance and contravariance

This is a hallmark of Kotlin, which is very much lacking in the case of Java. You can read about this in more detail in the documentation. I will merely give a couple of interesting examples as to problems that can arise when using RxJava in Kotlin.

Covariance:

Insofar as the Observable is the Java interface, this code does not compile. This is because the Java generic types are invariant. You can, of course, use out, but then the use of operators like scan will again lead to a compilation error:

A hallmark of the scan operator is that its generic type, T, is at once both input and output. If the Observable were the Kotlin interface, it would be possible to designate its T type as out and this would solve the problem:

And here is an example with contravariance:

For the same reason as in the previous example (generic types in Java are invariant), this example does not compile. You can solve the problem by adding in, however, once again, this is not a 100% solution:

And, in keeping with tradition, this problem is resolved in Kotlin by using in in the interface:

Thus, variance/contravariance of generic types is the third natural advantage of the Reaktive library.

Kotlin + Reactive = Reaktive

Let’s move on now to the main issue, namely describing the Reaktive library.

Here are some of its hallmarks:

  1. It is multiplatform which means that, at last, we can write shared code. At Badoo we consider this to be one of its most important advantages.
  2. It is written in Kotlin, which gives us the advantages described above: there are none of the limitations associated with null and variance/contravariance. This increases flexibility and provides compile-time safety.
  3. It is not dependent on other libraries, such as RxJava, RxSwift and so on, so there is no need to align library functionality with a common denominator.
  4. It has clean API. For example, the ObservableSource interface in Reaktive is simply called Observable, and all the operators are extension functions, located in separate files. There are no 15,000-line ‘God’ classes. This makes it easy to expand functionality without making changes to the existing interfaces and classes.
  5. It supports schedulers (using the usual operators, subscribeOn and observeOn).
  6. It features true multithreading for Kotlin/Native (limitations)
  7. Its interoperability with RxJava2 provides conversion of sources between Reaktive and RxJava2 and the ability to reuse schedulers from RxJava2.
  8. Follows ReactiveX.

I would like to say something more about the advantages we derived from the library being written in Kotlin.

In Reaktive null values are permitted, because in Kotlin this is safe. Here are some interesting examples:

Variance is also a major advantage. For example, in the Observable interface the T type is designated as out, which enable you to write something along the following lines:

This is what the library looks like the following day:

  • Status at the time of writing the article: alpha (some changes to the public API are possible);
  • Supported platforms: JVM, Android, JavaScript, Linux X64 and Linux ARM 32 hfp;
  • Supported sources: Observable, Maybe, Single and Completable;
  • Quite a large number of operators are supported, including map, filter, flatMap, concatMap, combineLatest, zip, merge and others (you can find a full list on GitHub);
  • The following schedulers are supported: computation, IO, trampoline and main;
  • Subjects: PublishSubject and BehaviorSubject;
  • Currently, backpressure is not supported. However, we are considering whether this may be useful and how to implement it.

What we are planning in the near future:

  • To use Reaktive in our products (at present we are looking at the possibilities)
  • Developing support for iOS (in progress)
  • Publishing artifacts in JCenter (at present we use the JitPack service)
  • Writing documentation;
  • Increasing the number of supported operators
  • Covering the code with tests

The library is already available to try out; you will find everything you need on GitHub. Do share your experience of using it and ask questions. Your feedback would be much appreciated :).

--

--