📜 ⬆️ ⬇️

Reaktive - a multiplatform library for reactive Kotlin



Many today love reactive programming. It has a lot of advantages: the absence of the so-called " callback hell ", the built-in error handling mechanism, and the functional programming style, which reduces the likelihood of bugs. Much easier to write multi-threaded code and easier to manage data streams (merge, split and convert).

For many programming languages, there is a reactive library: RxJava for JVM, RxJS for JavaScript, RxSwift for iOS, Rx.NET, etc.
')
But what do we have for Kotlin? It would be logical to assume that RxKotlin. And, indeed, such a library exists, but it is just a set of extensions for RxJava2, the so-called “sugar”.

And ideally, I would like to have a solution that meets the following criteria:


We at Badoo decided not to wait for the weather by the sea and made such a library. As you might have guessed, we called it Reaktive and uploaded it to GitHub .

In this article we will take a closer look at the expectations from reactive programming at Kotlin and see how they fit the capabilities of Reaktive.

Three Natural Reaktive Benefits


Multiplatform


The first natural advantage is most important. Currently, our iOS, Android, and Mobile Web teams exist separately. The requirements are common, the design is the same, but each team does its own work.

Kotlin allows you to write a multiplatform code, but you will have to forget about reactive programming. And I would like to be able to write shared libraries using reactive programming and distribute them within the company or upload to GitHub. Potentially, this approach can significantly reduce development time and reduce the total amount of code.

Null safety


This is more about the lack of Java and RxJava2. In short, null cannot be used. Let's try to figure out why. Take a look at this java interface:

 public interface UserDataSource {   Single<User> load(); } 

Can the result be null? To eliminate ambiguities, null is not allowed in RxJava2. And if you still need, that is Maybe and Optional. But in Kotlin there are no such problems. We can say that Single and Single <User?> Are different types, and all the problems emerge at the compilation stage.

Covariance and contravariance


This is a distinctive feature of Kotlin, something that is very lacking in Java. Details about this can be found in the manual . I will give just a couple of interesting examples of what problems arise when using RxJava in Kotlin.

Covariance :

 fun bar(source: Observable<CharSequence>) { } fun foo(source: Observable<String>) {   bar(source) //   } 

Since Observable is a Java interface, such code will not compile. This is because generic types in Java are invariant. You can, of course, use out, but then using statements like scan will again lead to a compilation error:

 fun bar(source: Observable<out CharSequence>) {   source.scan { a, b -> "$a,$b" } //   } fun foo(source: Observable<String>) {   bar(source) } 

The scan operator is different in that its generic type "T" is immediately both input and output. If Observable were a Kotlin interface, then its type T could be designated as out and this would solve the problem:

 interface Observable<out T> {   … } 

Here is an example with contravariance:

 fun bar(consumer: Consumer<String>) { } fun foo(consumer: Consumer<CharSequence>) {   bar(consumer) //   } 

For the same reason as in the previous example (generic types in Java are invariant), this example is not compiled. Adding in will solve the problem, but again not one hundred percent:

 fun bar(consumer: Consumer<in String>) {   if (consumer is Subject) {       val value: String = consumer.value //     } } fun foo(consumer: Consumer<CharSequence>) {   bar(consumer) } interface Subject<T> : Consumer<T> {   val value: T } 

Well, according to the tradition in Kotlin, this problem is solved by using in in the interface:

 interface Consumer<in T> {   fun accept(value: T) } 

Thus, the variance and contravariance of generic types are the third natural advantage of the Reaktive library.

Kotlin + Reactive = Reaktive


We turn to the main thing - the description of the Reaktive library.

Here are some of its features:

  1. It is multiplatform, which means that you can finally write common code. We at Badoo consider this one of the most important benefits.
  2. Written in Kotlin, which gives us the advantages described above: there are no restrictions on null, variation / contravariance. This increases flexibility and provides security during compilation.
  3. There is no dependence on other libraries, such as RxJava, RxSwift, etc., which means that there is no need to reduce the functionality of the library to a common denominator.
  4. Pure API. For example, the ObservableSource interface in Reaktive is simply called Observable , and all operators are extension-functions located in separate files. There are no God classes of 15,000 lines. This makes it possible to easily increase the functionality without making changes to existing interfaces and classes.
  5. Scheduler support (the familiar subscribeOn and observeOn operators are observeOn ).
  6. Compatibility with RxJava2 (interoperability), which provides source conversion between Reaktive and RxJava2 and the ability to reuse schedulers from RxJava2.
  7. ReactiveX compliance.

I would like to tell a little more about the benefits that we have received due to the fact that the libraries at Kotlin.

  1. In Reaktive, null values ​​are allowed, because in Kotlin it is safe. Here are some interesting examples:
    • observableOf<String>(null) //
    • val o1: Observable<String?> = observableOf(null)
    • val o2: Observable<String?> = o1 // ,
    • val o1: Observable<String?> = observableOf(null)
    • val o2: Observable<String?> = o1.notNull() // , null
    • val o1: Observable<String?> = observableOf("Hello")
    • val o2: Observable<String?> = o1 //
    • val o1: Observable<String?> = observableOf(null)
    • val o2: Observable<String?> = observableOf("Hello")
    • val o3: Observable<String?> = merge(o1, o2) //
    • val o4: Observable<String?> = merge(o1, o2) // ,

    Variance is also a big advantage. For example, in the Observable interface, type T is declared as out , which makes it possible to write something like the following:

     fun foo() {   val source: Observable<String> = observableOf("Hello")   bar(source) //   } fun bar(source: Observable<CharSequence>) { } 

This is how the library looks today:


What are our plans for the near future:


You can try the library now, you will find everything you need on GitHub . Share your experiences and ask questions. We will be grateful for any feedback.

Source: https://habr.com/ru/post/447424/


All Articles