Kotlin extensions to the RxJavaFX framework
Kotlin
Latest commit ebd9cac Dec 24, 2016 @thomasnield add eagerConnect()

README.md

RxKotlinFX

Kotlin extensions to the RxJavaFX library. This framework works especially well with TornadoFX. See a fully functional demo here!

Documentation

Learning RxJava with JavaFX (EAP)

Binaries

Binaries and dependency information for Maven, Ivy, Gradle and others can be found at http://search.maven.org.

Example for Maven:

<dependency>
    <groupId>com.github.thomasnield</groupId>
    <artifactId>rxkotlinfx</artifactId>
    <version>x.y.z</version>
</dependency>

Gradle:

repositories {
    mavenCentral()
}
dependencies {
    compile 'com.github.thomasnield:rxkotlinfx:x.y.z'
}

Contributing

Feel free to contribute and help streamline a pragmatic UI stack with Kotlin, RxJava, and JavaFX. Speaking of stacks, this project may be used in conjunction with TornadoFX and RxKotlin. Please make sure no extension naming conventions conflict with these two other libraries :)

Features

RxKotlinFX is the convergence of interoperability between RxJava, JavaFX, and Kotlin.

RxKotlinFX contains Kotlin extensions to RxJavaFX as well as additional Observable operators specific to JavaFX. It also is in exporatory stages to add helpful Node extension functions that return Observables. This exploration is inspired by the JavaFX/Kotlin interop project TornadoFX. Where TornadoFX handles layouts, node extensions, DI, and other JavaFX/Kotlin interoperations, this library will seek to integrate RxJava with JavaFX in the same spirit using Kotlin. The vision is to add useful extensions that put Observable streams as properties and functions on JavaFX components, especially where ObservableValue properties are not readily available.

RxJavaFX Extensions

The core API implements RxJavaFX static factories as extension functions.

Target Type Extension Function Description
Observable<T> toBinding() Subscribes the Observable<T> to a JavaFX Binding implementation. Calling dispose() will unsubscribe from the Observable<T>
Observable<T> toLazyBinding() Subscribes the Observable<T> to a lazy JavaFX Binding implementation, delaying subscription until a value is needed. Calling dispose() will unsubscribe from the Observable<T>
Property<T> bind(observable: Observable<T>) Binds a Property<T> to the emissions of an Observable<T>, and returns the Binding
Binding<T> addTo(compositeBinding: CompositeBinding) Adds the Binding to a CompositeBinding, and returns the Binding
ObservableValue<T> toObservable() Turns a JavaFX ObservableValue<T> into an RxJava Observable<T> that emits the latest value
ObservableValue<T> toObservableChanges() Turns a JavaFX ObservableValue<T> into an RxJava Observable<Change<T>> that emits the old value and new value as a pair
Dialog<T> toObservable<T> Returns an Observable<T> that emits the given result of Dialog<T>. Will be empty if no response.
Node, Window, Scene events(eventType: EventType) Creates an Observable emitting events of the given EventType
Node, MenuItem, ContextMenu actionEvents() Creates an Observable that emits an ActionEvent every time one occurs
ObservableList<T> onChangedObservable() Returns an Observable<ObservableList<T>> that emits the entire ObservableList<T> every time it is modified.
ObservableList<T> additions() Creates an Observable<T> emitting T items that were added to the ObservableList<T>
ObservableList<T> removals() Creates an Observable<T> emitting T items that were removed from the ObservableList<T>
ObservableList<T> updates() Creates an Observable<T> emitting T items whose specified properties were updated in the ObservableList<T>
ObservableList<T> changes() Creates an Observable<ListChange<T>> emitting ListChange<T> items, which pairs each item with an ADDED, REMOVED, or UPDATED flag
ObservableList<T> distinctChanges() Creates an Observable<ListChange<T>> emitting distinct ListChange<T> items. It will only emit the first ADDED item T and not emit dupes, and will only emit the REMOVED item T when no more dupes exist
ObservableList<T> distinctChanges(mapper: (T) -> R) Creates an Observable<ListChange<T>> emitting distinct ListChange<T> items based off the mapper's definition of a distinct value R. It will only emit the first ADDED item T and not emit dupes, and will only emit the REMOVED item T when no more dupes exist
ObservableList<T> distinctMappingChanges(mapper: (T) -> R) Creates an Observable<ListChange<R>> emitting distinct ListChange<R> mappings based off the mapper's definition of a distinct value R. It will only emit the first ADDED item R and not emit dupes, and will only emit the REMOVED item R when no more dupes exist
Observable of Button ActionEvents
val myButton = Button("Press Me")
val subscription = myButton.actionEvents().subscribe { println("Pressed!") } 
Creating a Reactive Binding
val myButton = Button("Press Me")

val countBinding = myButton.actionEvents().map { 1 }
    .scan(0, { x,y -> x + y })
    .map { it.toString() }
    .toBinding()

val myLabel = Label()
myLabel.textProperty().bind(countBinding)
Observable of ObservableList Events
val items = FXCollections.observableArrayList("Alpha", "Beta", "Gamma")

val changes = items.changes()

changes.filter { it.flag == Flag.ADDED }
        .map { it.value }
        .subscribe { println("ADDED $it") }

items.add("Delta")
items.add("Epsilon")
OUTPUT
ADDED Delta
ADDED Epsilon
Turning an ObservableList into a Hot Concatenation
val observableList = FXCollections.observableArrayList<String>()

observableList.onChangedObservable()
        .flatMap {
            it.toObservable().map { it.length }
                    .map { it.toString() }
                    .reduce { s1,s2 -> s1 + "|"  + s2  }
        }
        .subscribe { println(it) }

observableList.setAll("Alpha", "Beta", "Gamma")
observableList.add("Delta")
observableList.add("Epsilon")
observableList.remove("Alpha")
OUTPUT
5|4|5
5|4|5|5
5|4|5|5|7
4|5|5|7
Using a Dialog or Alert
val dialog = Alert(AlertType.CONFIRMATION, "Are you sure you want to continue?")

dialog.toObservable().filter { it == ButtonType.YES }
    .subscribe { println("You pressed YES") } 
Using and Disposing CompositeBinding
val binding1: Binding = ...
val binding2: Binding = ... 

//adding one at a time
val bindings = CompositeBinding()
val bindings += binding1
val bindings += binding2

//or all at once
val bindings = CompositeBinding(binding1,binding2)

//do stuff, then dispose Bindings
bindings.dispose()
Using a CompositeObservable

A CompositeObservable is effectivey an Observable.merge() but it allows adding and removing Observables at any time. This is helpful for centralizing UI events into a model. Read more about the CompositeObservable here

val compositeObservable = CompositeObservable()

//this subscriber will receive emissions from source1 and source2
compositeObservable.subscribe { doSomethingWith(it) } 

val source1: Observable<Int> = ...
compositeObservable += source1

val source2: Observable<Int> = ...
compositeObservable += source2

//subscriber will no longer receive emissions from source1
compositeObservable -= source1 

Operators

RxKotlinFX has a growing list of operators placed as extension functions onto Observable that aid interoperability with JavaFX.

Operator Description
observeOnFx() Schedules the emissions to be observed on the JavaFX thread
subscribeOnFx() Schedules the source Observable to emit items on the JavaFX thread
doOnNextFx() Executes the specified action on the FX thread for each emission
doOnErrorFx() Executes the specified action on the FX thread when an error is emitted
doOnCompletedFx() Executes the specified action on the FX thread when the Observable calls onCompleted()
doOnSubscribeFx() Executes the specified action on the FX thread when the Observable is first subscribed
doOnTerminateFx() Executes the specified action on the FX thread when the Observable calls onCompleted() or onError()
doOnUnsubscribeFx() Executes the specified action on the FX thread when the Observable is unsubscribed
doOnNextCount() Executes the specified action with the cumulative count of emissions for that emission
doOnErrorCount() Executes the specified action with the cumulative count of emissions when an error is emitted
doOnCompletedCount() Executes the specified action with the total emission count when onCompleted() is called
doOnNextCountFx() Same as doOnNextCount() except action is executed on FX thread
doOnErrorCountFx() Same as doOnErrorCount() except action is executed on FX thread
doOnCompletedCountFx() Same as doOnCompletedCount() except action is executed on FX thread

The doOnXXXCount() operators are especially helpful for providing a status update of how many items have been "processed" by an Observable.

val source = Observable.range(1,1000)
val processedCountLabel = Label()

source.map { it * 10 }
     .doOnNextFx { processedCountLabel.text = "Processed $it items" }
     .subsribe { doSomethingWith(it) }

Control Extensions

The rest of the project will likely add convenient extension functions to emit events as Observable values, much like the TornadoFX project has done. For example, helpful Observable extension functions and properties can be added to TableView and ListView, such as selection events.

val tableView: TableView<MyItem> = ...
val selections: Observable<MyItem> = tableView.itemSelections
val rowIndexSelections: Observable<Int> = tableView.rowIndexSelections

Check releases as well the Nodes code file to see a list of available extensions. Feel free to contribute if you see any missing.

Bugs and Feedback

For bugs, questions and discussions please use the Github Issues.

LICENSE

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.