https://github.com/torakiki/RxJavaFX
Read the free eBook Learning RxJava with JavaFX to get started.
RxJavaFX is a lightweight library to convert JavaFX events into RxJava Observables/Flowables and vice versa. It also has a Scheduler
to safely move emissions to the JavaFX Event Dispatch Thread.
NOTE: To use with Kotlin, check out RxKotlinFX to leverage this library with extension functions and additional operators.
Learning RxJava with JavaFX - Free eBook that covers RxJava from a JavaFX perspective.
Learning RxJava - Packt book covering RxJava 2.0 in depth, with a few RxJavaFX examples.
Binaries and dependency information for Maven, Ivy, Gradle and others can be found at http://search.maven.org.
Example for Maven:
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjavafx</artifactId>
<version>1.x.y</version>
</dependency>
Gradle:
dependencies {
compile 'io.reactivex:rxjavafx:1.x.y'
}
RxJavaFX 2.x versions uses a different group ID io.reactivex.rxjava2
to prevent clashing with 1.x dependencies. Binaries and dependency information for Maven, Ivy, Gradle and others can be found at http://search.maven.org.
Example for Maven:
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjavafx</artifactId>
<version>2.x.y</version>
</dependency>
Gradle:
dependencies {
compile 'io.reactivex.rxjava2:rxjavafx:2.x.y'
}
RxJavaFX has a comprehensive set of features to interop RxJava with JavaFX:
Node
, ObservableValue
, ObservableList
, and other component events into an RxJava Observable
Observable
or Flowable
into a JavaFX Binding
. You can get event emissions by calling JavaFxObservable.eventsOf()
and pass the JavaFX Node
and the EventType
you are interested in. This will return an RxJava Observable
.
Button incrementBttn = new Button("Increment");
Observable<ActionEvent> bttnEvents =
JavaFxObservable.eventsOf(incrementBttn, ActionEvent.ACTION);
Action events are common and do not only apply to Node
types. They also emit from MenuItem
and ContextMenu
instances, as well as a few other types.
Therefore, a few overloaded factories are provided to emit ActionEvent
items from these controls
Button incrementBttn = new Button("Increment");
Observable<ActionEvent> bttnEvents =
JavaFxObservable.actionEventsOf(incrementBttn);
MenuItem menuItem = new MenuItem("Select me");
Observable<ActionEvent> menuItemEvents =
JavaFxObservable.actionEventsOf(menuItem);
There are also factories provided to convert events from a Dialog
, Window
or Scene
into an Observable
. If you would like to see factories for other components and event types, please let us know or put in a PR.
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Confirmation");
alert.setHeaderText("Please confirm your action");
alert.setContentText("Are you ok with this?");
JavaFxObservable.fromDialog(alert)
.filter(response -> response.equals(ButtonType.OK))
.subscribe(System.out::println,Throwable::printStackTrace);
Observable<MouseEvent> sceneMouseMovements =
JavaFxObservable.eventsOf(scene, MouseEvent.MOUSE_MOVED);
sceneMouseMovements.subscribe(v -> System.out.println(v.getSceneX() + "," + v.getSceneY()));
Observable<WindowEvent> windowHidingEvents =
JavaFxObservable.eventsOf(primaryStage,WindowEvent.WINDOW_HIDING);
windowHidingEvents.subscribe(v -> System.out.println("Hiding!"));
Not to be confused with the RxJava Observable
, the JavaFX ObservableValue
can be converted into an RxJava Observable
that emits the initial value and all value changes.
TextField textInput = new TextField();
Observable<String> textInputs =
JavaFxObservable.valuesOf(textInput.textProperty());
Note that many Nodes in JavaFX will have an initial value, which sometimes can be null
, and you might consider using RxJava's skip()
operator to ignore this initial value.
For every change to an ObservableValue
, you can emit the old value and new value as a pair. The two values will be wrapped up in a Change
class and you can access them via getOldVal()
and getNewVal()
. Just call the JavaFxObservable.changesOf()
factory.
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 100);
Spinner spinner = new Spinner<>();
spinner.setValueFactory(svf);
spinner.setEditable(true);
Label spinnerChangesLabel = new Label();
Subscription subscription = JavaFxObservable.changesOf(spinner.valueProperty())
.map(change -> "OLD: " + change.getOldVal() + " NEW: " + change.getNewVal())
.subscribe(spinnerChangesLabel::setText);
There are several factories to emit many useful ObservableList
, ObservableMap
, and ObservableSet
events as Observables. These all can be found as static factory methods in the JavaFxObservable
static class.
Factory Method | Parameter Type | Return Type | Description |
---|---|---|---|
emitOnChanged() | ObservableList<T> | Observable<ObservableList<T>> | Emits the entire ObservableList every time it changes |
additionsOf() | ObservableList<T> | Observable<T> | Emits additions to an ObservableList |
removalsOf() | ObservableList<T> | Observable<T> | Emits removals from an ObservableList |
updatesOf() | ObservableList<T> | Observable<ListChange<T>> | Emits every item that was the result of a change to an ObservableList , with an ADDED , REMOVED , or UPDATED flag |
distinctChangesOf() | ObservableList<T> | Observable<ListChange<R>> | Emits only distinct addtions and removals to an ObservableList |
distinctMappingsOf() | ObservableList<T>, Func1<T,R> | Observable<ListChange<R>> | Emits only distinct additions and removals to an ObservableList and emits the mapping |
distinctChangesOf() | ObservableList<T>, Func1<T,R> | Observable<ListChange<R>> | Emits only distinct additions and removals to an ObservableList based on a mapping |
emitOnChanged() | ObservableMap<K,T> | Observable<ObservableMap<K,T>> | Emits the entire ObservableMap every time it changes |
additionsOf() | ObservableMap<K,T> | Observable<Map.Entry<K,T>> | Emits every Map.Entry<K,T> added to an ObservableMap |
removalsOf() | ObservableMap<K,T> | Observable<Map.Entry<K,T>> | Emits every Map.Entry<K,T> removed from an ObservableMap |
changesOf() | ObservableMap<K,T> | Observable<MapChange<K,T>> | Emits every key/value pair with an ADDED or REMOVED flag. |
emitOnChanged() | ObservableSet<T> | Observable<ObservableSet<T>> | Emits the entire ObservableSet every time it changes |
additionsOf() | ObservableSet<T> | Observable<T> | Emits every addition to an ObservableSet |
removalsOf() | ObservableSet<T> | Observable<T> | Emits every removal to an ObservableSet |
changesOf() | ObservableSet<T> | Observable<SetChange<T> | Emits every item ADDED or REMOVED item from an ObservableSet with the corresponding flag |
You can convert an RxJava Observable
into a JavaFX Binding
by calling the JavaFxObserver.toBinding()
factory. Calling the dispose()
method on the Binding
will handle the unsubscription from the Observable
. You can then take this Binding
to bind other control properties to it.
Button incrementBttn = new Button("Increment");
Label incrementLabel = new Label("");
Observable<ActionEvent> bttnEvents =
JavaFxObservable.eventsOf(incrementBttn, ActionEvent.ACTION);
Observable<String> accumulations = bttnEvents.map(e -> 1)
.scan(0,(x, y) -> x + y)
.map(Object::toString);
Binding<String> binding = JavaFxObserver.toBinding(accumulations);
incrementLabel.textProperty().bind(binding);
//do stuff, then dispose Binding
binding.dispose();
It is usually good practice to specify an onError
to the Binding
, just like a normal Observer
so you can handle any errors that are communicated up the chain.
incrementLabel.textProperty().bind(binding, e -> e.printStackTrace());
The toBinding()
factory above will eagerly subscribe the Observable
to the Binding
implementation. But if you want to delay the subscription to the Observable
until the Binding
is actually used (specifically when its getValue()
is called), use toLazyBinding()
instead.
Binding<String> lazyBinding = JavaFxObserver.toLazyBinding(myObservable);
This can be handy for data controls like TableView
, which will only request values for records that are visible. Using the toLazyBinding()
to feed column values will cause subscriptions to only happen with visible records.
You also have the option to use a CompositeBinding
to group multiple Binding
s together, and dispose()
them all at once. It is the JavaFX equivalent to CompositeSubscription
.
Binding<Long> binding1 = ...
bindings.add(binding1);
Binding<Long> binding2 = ...
bindings.add(binding2);
//do stuff on UI, and dispose() both bindings
bindings.dispose();
When you update any JavaFX control, it must be done on the JavaFX Event Dispatch Thread. Fortunately, the JavaFxScheduler
makes it trivial to take work off the JavaFX thread and put it back when the results are ready. Below we can use the observeOn()
to pass text value emissions to a computation thread where the text will be flipped. Then we can pass JavaFxScheduler.platform()
to another observeOn()
afterwards to put it back on the JavaFX thread. From there it will update the flippedTextLabel
.
TextField textInput = new TextField();
Label fippedTextLabel = new Label();
Observable<String> textInputs =
JavaFxObservable.valuesOf(textInput.textProperty());
sub2 = textInputs.observeOn(Schedulers.computation())
.map(s -> new StringBuilder(s).reverse().toString())
.observeOn(JavaFxScheduler.platform())
.subscribe(fippedTextLabel::setText);
There is a JavaFX equivalent to Observable.interval()
that will emit on the JavaFX thread instead. Calling JavaFxObservable.interval()
will push consecutive Long
values at the specified Duration
.
Observable<Long> everySecond = JavaFxObservable.interval(Duration.millis(1000));
ReactFX is a popular API to implement reactive patterns with JavaFX using the EventStream
. However, RxJava uses an Observable
and the two are not (directly) compatible with each other.
Although ReactFX has some asynchronous operators like threadBridge
, ReactFX emphasizes synchronous behavior. This means it encourages keeping events on the JavaFX thread. RxJavaFX, which fully embraces RxJava and asynchronous design, can switch between threads and schedulers with ease. As long as subscriptions affecting the UI are observed on the JavaFX thread, you can leverage the powerful operators and libraries of RxJava safely.
If you are heavily dependent on RxJava, asynchronous processing, or do not want your entire reactive codebase to be UI-focused, you will probably want to use RxJavaFX.
If you are building your JavaFX application with Kotlin, check out RxKotlinFX to leverage this library through Kotlin extension functions.
For bugs, questions and discussions please use the Github Issues.
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.