javajavafx

JavaFX bidirectional bindings desync


I'm starting to learn JavaFX for a personal project, and I thought it would be a good idea to try to practice some basic things first. I decided to first sync two FieldText items so each one would reflect the changes made to the other.

Problem is, it keeps desyncing. After some unspecified time (or some unspecified, non-consistent number of uses), whether I decide to change from writing in one field to the other, or even just keep writing in one of them, the other field stops reflecting the changes. At that point, no matter what I do, the bind is broken and no field reflects what is done to the other.

Here's what I'm doing, hopefully someone can point out what I'm doing wrong:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class MainWindow extends Application {
    StringConverter<String> conv;

    @Override
    public void start(Stage primaryStage) {
        BorderPane borderPane = new BorderPane();
    
        StringProperty test = new SimpleStringProperty("");
        TextField tf1 = new TextField();
        TextField tf2 = new TextField();
        conv = new StringConverter<String>() {

            @Override
            public String fromString(String str) {
                if (str == null)
                    return "";
                return str;
            }

            @Override
            public String toString(String str) {
                if (str == null)
                    return "";
                return str;
            }
        };

    
    tf1.textProperty().bindBidirectional(test, conv);
    tf2.textProperty().bindBidirectional(test, conv);
    
    Label currentValLabel = new Label(""+test.get());
    test.addListener((obs, oldVal, newVal) -> {
        currentValLabel.setText("Valor: "+ newVal);
    });
    
    
    borderPane.setTop(tf1);
    borderPane.setBottom(tf2);
    borderPane.setCenter(currentValLabel);
    Scene scene = new Scene(borderPane, 600, 400);
    primaryStage.setTitle("BorderPane en JavaFX");
    primaryStage.setScene(scene);
    primaryStage.show();
}

}


Solution

  • The test property instance is most likely being garbage collected. When that happens the bidirectional bindings "break". Meaning updating one text field no longer updates test (because it doesn't exist) and so neither the other text field nor the label get updated.

    A bidirectional binding explicitly does not prevent either of the two properties from being garbage collected. This is documented:

    JavaFX bidirectional binding implementation use weak listeners. This means bidirectional binding does not prevent properties from being garbage collected.

    The solution is to maintain a strong reference to test. Probably the best way to implement that is to store the test property in a field of your MainWindow class.


    Doing the following, at least in JavaFX 24, is another way to keep a strong reference to test:

    // import javafx.beans.binding.Bindings
    currentLabel.textProperty().bind(Bindings.concat("Valor: ", test));
    

    You would do this instead of adding a ChangeListener to test. Though I'm not sure that the documentation of either Property::bind(ObservableValue) or Bindings::concat(Object...) strictly guarantee the observable(s) will be strongly referenced. If I'm wrong about that, please someone let me know in the comments.