Presented at the New York Kotlin Meetup July 11th, 2018
I take a look at how some feature of the Kotlin language can make working with RxJava really great. I also briefly discuss the RxKotlin project and how/why you might choose to use parts of it.
you can move the lambda describing the function outside the parentheses for the call This reduces a couple keystrokes and helps improve readability of scopes, especially when they get nested // Definition: fun filter(predicate: () -> Boolean) = /* impl */ // "Java-style": Observable.just(1,11, 111, 1111, 11111) .filter({ it > 100 }) .subscribe({ System.out.print(it) }) // Kotlin-style: Observable.just(1,11, 111, 1111, 11111) .filter { it > 100 } .subscribe { System.out.print(it) }
may receive multiple functions or are overloaded with different functionally equivalent types that may behave differently Notable example of this: #startWith(T) vs #startWith(ObservableSource) // Example: var a = Observable.just("b", "c") a.startWith { "a" } .subscribe(System.out::println) // Expected? a b c // Actual: <no emission>
access data from fields that are in a container object like a Tuple data class Container(val a: String, val b: String, val c: String, val d: String) // Reference the field: Observable.combineLatest(a, b, c, d, ::Container) .doOnNext { container -> System.out.println(container.a) } .subscribe { System.out.println(it.toString()) } // Destructure and access field directly: Observable.combineLatest(a, b, c, d, ::Container) .doOnNext { (a,_,_,_) -> System.out.println(a) } .subscribe { System.out.println(it.toString()) }
remove null emissions from the stream. This certainly makes life better on the Java side by better defining the Observable contract. In Kotlin however, we may have preferred to just rely on the type system to enforce the difference between Observable<String> and Observable<String?> // Nullable types? val subject = PublishSubject.create<String?>() subject.subscribe { System.out.println(it.toString()) } subject.onNext("Hello") subject.onNext(null) // nope!
Optional type. This can be functionally very similar to the Guava Optional without any baggage (this example inspired by Jake Wharton and Alex Strong's talk at Kotlinconf) sealed class Optional<out T : Any> { data class Present<out T : Any>(val value: T) : Optional<T>() object None : Optional<Nothing>() } fun <T : Any> T?.asOptional() = if (this == null) Optional.None else Optional.Present(this) // Usage: val subject = PublishSubject.create<Optional<String>>() subject.subscribe { when (it) { is Optional.Present<String> -> println("Success") is Optional.None -> println("Failure") } } subject.onNext("Hello".asOptional()) subject.onNext(Optional.None)
player using an object that contains the State of playback as an enum public final class MusicPlayerState { State state; float progress; long trackDurationS; String trackTitle; String trackDescription Throwable error; String errorMessage; } val state = BehaviorSubject.create<MusicPlayerState>() state.onNext(new MusicPlayerState(State.NO_TRACK, 0.0, 0, "", "", null, "")) state.onNext(new MusicPlayerState(State.TRACK_QUEUED, 0.0, 193, "Last Night", "The Strokes - Is This It", null, ""))
cleanly and make our call sites a breeze data class TrackInfo(val durationS: Long, val trackTitle: String, val trackDescription: String) sealed class MusicPlayerState { object NoTrack : MusicPlayerState() data class TrackQueued(val trackInfo: TrackInfo) data class TrackPlaying(val trackInfo: TrackInfo, val progress: Float) data class TrackPaused(val trackInfo: TrackInfo, val progress: Float) data class TrackCompleted(val trackInfo: TrackInfo) data class PlaybackError(val trackInfo: TrackInfo, val error: Throwable, val errorMessage: String) } val state = BehaviorSubject.create<MusicPlayerState>() state.onNext(MusicPlayerState.NoTrack) val trackInfo = TrackInfo(193, "Last Night", "The Strokes - Is This It") state.onNext(MusicPlayerState.TrackQueued(trackInfo)) state.onNext(MusicPlayerState.TrackPlaying(trackInfo, 0.0)) state.onNext(MusicPlayerState.TrackPlaying(trackInfo, 0.05))
framework would be publishing updates to an object that represents the state of a view. For more information about this pattern, come see my talk at Droidcon NYC in September! val subject: BehaviorSubject<StringContainer> = BehaviorSubject.create() fun publishUpdate(updatedString: String) { val value = subject.value val newValue = value?.copy(string = updatedString) ?: StringContainer(updatedString) subject.onNext(newValue) }
APIs for transitioning between data types. Common examples are things like turning collections into streams of their values. You might also see extensions for translating from other asynchronous constructs, e.g. Future.toObservable() // "stock" creating observable from list: val strings: List<String> = loadStrings() Observable.fromIterable(strings) … // then add this: fun <T : Any> Iterable<T>.toObservable(): Observable<T> = Observable.fromIterable(this) // now our call becomes: val strings: List<String> = loadStrings() strings.toObservable() …
by replacing it with an extension function that's both easier to read and test. fun <T> efficientCollect(listSize: Int): Observable.Transformer<T, List<T>> { return Observable.Transformer { observable -> observable.collect( { ArrayList<T>(listSize) as List<T> }, { listings, listing -> (listings as ArrayList).add(listing) } ) } // call-site: observable.compose(Utils.efficientCollect(size)) // versus fun <T> Observable<T>.efficientCollect(listSize: Int): Observable<List<T>> { return this .collect( { ArrayList<T>(listSize) as List<T> }, { listings, listing -> (listings as ArrayList).add(listing) } ) } // call-site: observable.efficientCollect(size)