Skip to content

Start implementation of BindingSubscriber #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 12, 2016

Conversation

thomasnield
Copy link
Collaborator

Whomever is watching this project, I am starting the implementation of Binding support in a similar vein as ReactFX. The unfortunate constraint is since RxJavaFX does not own the RxJava Observable or Subscriber types, it will probably feel somewhat like an anti-pattern to pass an entire Observable to a static factory method, which subscribes to the Observable<T> and returns a Binding<T>.

Observable<String> source = Observable.just("Alpha","Beta","Gamma");

Binding binding = JavaFxSubscriber.toBinding(source.map(String::length));

Unfortunately, there is no compose() equivalent for Subscribers that I know of. I cannot extend two classes for both Binding and ObservableValueBase, so I chose the latter.

As a recent convert to Kotlin, here is a biased PSA: Check out Kotlin and use its extension functions/methods if you want to "add" a toBinding() method to an Observable.

Please take a look at my PR and let me know if you have any suggestions to make this API as intuitive as possible.

@thomasnield
Copy link
Collaborator Author

Got it. I was able to make BindingSubscriber implement/extend both Binding and Subscriber. I am pretty sure that this is sound. I found it necessary to pass the Property and have the BindingSubscriber handle the binding.

I'll let this sit for a week if anybody has feedback on how the API is organized.

TextField textInput = new TextField();
Label fippedTextLabel = new Label();

Observable<String> textInputs = JavaFxObservable.fromObservableValue(textInput.textProperty())
                        .map(s -> new StringBuilder(s).reverse().toString());

Subscription sub  = textInputs.subscribe(JavaFxSubscriber.toBinding(fippedTextLabel.textProperty()));

@thomasnield
Copy link
Collaborator Author

Unfortunately, I don't think there is a way to return a Binding instead of a Subscriber without breaking the fluent RxJava pattern. I can't think of a way to do this without extension methods.

@thomasnield
Copy link
Collaborator Author

I may rename the method to bind(), instead of toBinding(). That would make the purpose more clear.

@manuel-mauky
Copy link
Contributor

In my oppinion your first proposal (Binding binding = JavaFxSubscriber.toBinding(source)) is more intuitive compared to the later proposal where a property has to be passed to the toBinding method.

See this example:

TextField textInput = new TextField();
Observable<String> textInputs = JavaFxObservable.fromObservableValue(textInput.textProperty())
                        .map(s -> new StringBuilder(s).reverse().toString());

Label fippedTextLabel = new Label();

Binding flippedText = JavaFxSubscriber.toBinding(textInputs);

flippedTextLabel.textProperty().bind(Bindings.concat("Flipped text is:", flippedText));

With the Binding instance I'm more flexible on how to use it. In the example I'm using the Binding to create a new Binding that has a concatenation of the Binding value.

Maybe it would be even better to return ObservableValue<T> instead of Binding?

With the second approach I would have to create an extra Property to implement this:

TextField textInput = new TextField();
Observable<String> textInputs = JavaFxObservable.fromObservableValue(textInput.textProperty())
                        .map(s -> new StringBuilder(s).reverse().toString());

Label fippedTextLabel = new Label();

StringProperty flippedText = new SimpleStringProperty();
textInputs.subscribe(JavaFxSubscriber.toBinding(flippedText));

flippedTextLabel.textProperty().bind(Bindings.concat("Flipped text is:", flippedText));

@thomasnield
Copy link
Collaborator Author

@lestard I forgot about those helper methods in Bindings. That's definitely a good point. I was actually thinking about those other considerations last night too. I was conflicted because I wasn't sure whether to return BindingSubscriber as Subscriber or a Binding. I was chasing to return a Subscriber because it didn't require passing an Observable to a static factory method, which feels like an anti-pattern in the RxJava world. But the truth is without extension functions like in C# or Kotlin, it is unavoidable.

So I think you are right. I should be returning a Binding or an ObservableValue<T> instead to minimize the intermediate steps needed. I'll implement this.

@thomasnield
Copy link
Collaborator Author

Okay you are right. This is much more intuitive and flexible.

public class RxJavaFXTest extends Application {

    private final Button incrementBttn;
    private final Label incrementLabel;

    private final TextField textInput;
    private final Label fippedTextLabel;

    private final Binding<String> binding1;
    private final Binding<String> binding2;

    public RxJavaFXTest() {

        //initialize increment demo
        incrementBttn = new Button("Increment");
        incrementLabel =  new Label("");

        Observable<String> bttnEvents =
                JavaFxObservable.fromNodeEvents(incrementBttn, ActionEvent.ACTION)
                        .map(e -> 1).scan(0,(x,y) -> x + y)
                        .map(Object::toString);

        binding1 = JavaFxSubscriber.toBinding(bttnEvents);

        incrementLabel.textProperty().bind(binding1);

        //initialize text flipper
        textInput = new TextField();
        fippedTextLabel = new Label();


        Observable<String> textInputs = JavaFxObservable.fromObservableValue(textInput.textProperty())
                        .observeOn(Schedulers.computation())
                        .map(s -> new StringBuilder(s).reverse().toString())
                        .observeOn(JavaFxScheduler.getInstance());


        binding2 = JavaFxSubscriber.toBinding(textInputs);

        fippedTextLabel.textProperty().bind(binding2);

    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        GridPane gridPane = new GridPane();

        gridPane.setHgap(10);
        gridPane.setVgap(10);

        gridPane.add(incrementBttn,0,0);
        gridPane.add(incrementLabel,1,0);

        gridPane.add(textInput,0,1);
        gridPane.add(fippedTextLabel, 1,1);

        Scene scene = new Scene(gridPane);
        primaryStage.setWidth(265);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        super.stop();

        binding1.dispose();
        binding2.dispose();
    }
}

It also makes it easier for us Kotlin people. We can easily turn this into an extension function on the Observable.

fun bindingTest() {
    val source = Observable.just("Alpha","Beta","Gamma");

    val binding = source.toBinding()
}

//extension declaration
fun <T> Observable<T>.toBinding() = JavaFxSubscriber.toBinding(this)

@thomasnield
Copy link
Collaborator Author

I think a Binding<T> has to be returned instead of an ObservableValue<T>, because the client needs access to the dispose() method.

@manuel-mauky
Copy link
Contributor

You are right: Returning Binding instead of ObservableValue should be fine.

thomasnield added a commit that referenced this pull request Feb 12, 2016
Start implementation of BindingSubscriber
@thomasnield thomasnield merged commit 01f2466 into ReactiveX:0.x Feb 12, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants