Using bind in JavaFX

    When I first became acquainted with the technique of variable binding, at first I wanted to bind everything in a row, it was so exciting. Like any technology, JavaFX and binding should not be used thoughtlessly. It should be remembered that binding is essentially a hidden implementation of the Observer pattern (or Listeners, as you like). As a result, many non-obvious problems can arise, such as “memory leaks”, performance problems, etc.

    In this post I would like to give a number of patterns and antipatterns of binding application in JavaFX. In addition, the second task is to publish answers to some questions that were often asked at Sun Tech Days when I was “on duty” at the JavaFX booth. It seems to me that many of these issues are poorly covered, and especially in Runet.

    So, to the point. It is assumed that the reader has at least some understanding of JavaFX. However, in case the reader knows only by hearsay about bind, I will give a simple example of its use:
    	value: String = "Hello, World";
    	def boundValue: String = bind "The title is: {value}";
    	FX.println ("{boundValue}");
    	value = "Yet another value";
    	FX.println ("{boundValue}");
    


    The bind keyword in the second line says that the value of boundVariable is always equal to the result of evaluating the expression following the keyword (“The title is: {value}”).

    If you run the application, we get:
    	cy6ergn0m @ cgmachine ~ / test / fx $ javafxc Main.fx
    	cy6ergn0m @ cgmachine ~ / test / fx $ javafx Main    
    	The title is: Hello, World
    	The title is: Yet another value
    


    No matter how the value of the value variable is changed, the boundVariable variable is updated automatically (due to the code hidden inside JavaFX runtime that registers a listener for the value variable).

    The expression used in linking can be either simple (one to one) or complex: you can call functions and arithmetic operations.

    It should be noted that if a function is called in a bind expression, then the dependencies are calculated in such a way that the variable is recalculated only if the variables involved in the expression itself are changed, and not those used inside the function used.

    For instance:
    	var a: Integer = 1;
    	var b: Integer = 2;
    	function test (value: Integer): Integer {
    	      value + b
    	}
    	def boundVariable: Integer = bind test (a);
    	FX.println ("bound: {boundVariable}");
    	a = 2;
    	FX.println ("bound: {boundVariable}");
    	b is 3;
    	FX.println ("bound: {boundVariable}");
    


    We execute and see:
    	cy6ergn0m @ cgmachine ~ / test / fx $ javafxc Main2.fx 
    	cy6ergn0m @ cgmachine ~ / test / fx $ javafx Main2
    	bound: 3
    	bound: 4
    	bound: 4
    	


    It is seen that when we updated the variable “a”, then boundVariable was recalculated, and when we updated “b”, nothing happened, since “b” is not used in the bind expression.

    JavaFX perceives the function participating in the expression as a “black box” and does not try to take into account possible side effects both from the side of the function and from the rest of the code (with the exception of the expression itself in bind, of course).

    This seemingly inferior behavior can be used to good. In this way, you can protect yourself from overly frequent recounting of the variable (useful in complex cases when the calculated expression creates complex and heavy objects). It often turns out that we don’t really want to recount the whole expression if ANY quantities involved in it change.

    In such cases, such “passive” parts can be carried inside a separate function. In addition, this can help to avoid cascading updates, when many variables depend on each other and begin to be updated many times. Sometimes the number of counts becomes so large, and the created objects are so heavy, the application performance can be severely affected.

    It should also be noted that EVERY change in a variable that some others are associated with leads to an immediate conversion of all dependent variables. So the assignment operation (a hidden call to the set method) will not end until all the dependencies are updated. If any other variables depend on the dependent variables, then they will also be recalculated, etc. all variables will be recursively updated. In this case, you can get a cyclic "infinite" update (it is of course finite, since it will end due to stack overflow).

    At the stand they asked questions about the possibility of dynamically creating objects / components in the application. In many cases, you can bind controls to the data model directly using bind. Then, when changes are made to the application data model, the display (UI) will be updated automatically.

    For instance,
    	import javafx.scene.Scene;
    	import javafx.stage.Stage;
    	import javafx.scene.control.Button;
    	import javafx.scene.Group;
    	var itemsList: String [] = ["One", "Two", "Three"];
    	function loadItems (): Void {
    	    // do interaction with server-side, read from file, etc ..
    	    itemsList = 
    	        for (i in [1..10])
    	                "element {i}";
    	}
    	Stage {
    	    width: 200
    	    height: 400
    	    scene: Scene {
    	        content: [
    	            Button {
    	                text: "Load ..."
    	                action: loadItems
    	            }
    	            Group {
    	                content: bind
    	                    for (item in itemsList)
    	                        Button {
    	                            text: "{item}"
    	                            height: 20
    	                            translateX: 10
    	                            translateY: 28 + 22 * ​​indexof item
    	                        }
    	            }
    	        ]
    	    }
    	}
    	


    If we run the application, we will see a window. The list of buttons under the “Load” button is associated with the “itemsList” list, and whenever we update it (itemsList list), the composition of the buttons also changes. Thus, we associated the display (buttons) with the application data model (in this case, just a list of strings). In addition, here we applied the binding of not just values, but associated sequences (Sequence, list in JavaFX terminology) with lists.

    application view after pressing the Load button

    It should be noted that when recalculating the expression for Group.content, all buttons will be recreated, and the old button instances will be destroyed. So if the application has a complex and large scene, then you should not associate it entirely with the data model, since frequent updates to the model will lead to frequent re-creation of all components / objects of the scene, which will lead to performance problems. At the same time, such binding is extremely convenient and reliable and there is much less chance of errors.

    The logic of the loadItems function could be implemented somehow more interesting, for example, one could call a web service and get data from the server.

    Among other things, linking works not only if we reassign the entire sequence, but also if we change only one of the elements of the sequence.
    For example, if we replace the loadItems function code with the following:
    	function loadItems (): Void {
    	    // do interaction with server-side, read from file, etc ..
    	    itemsList [1] = "Hey, Iam here!";
    	}
    


    Then, when you click on the Load button, the list of buttons below will also be recreated and our change will take effect.

    Among other things, I was also asked about the possibility of bind working in JavaFX when used with Java. The answer in this case is simple: if you update the JavaFX field from Java, then all the bind's will work, since from Java we call the set method for this field, which is generated by the JavaFX compiler. This method, in turn, will do everything necessary when updating the field value.

    However, in some cases the need arises for the opposite: you need to associate a JavaFX variable with a variable in Java. In this case, you will have to do this manually as follows:
    - create a Java listener interface
    - create a kind of Java-JavaFX adapter on JavaFX that implements this interface and contains a public-read field, and a certain method adds it to the list of Java code listeners;
    - when something changes in the Java code, the Java code calls all listeners, including our JavaFX listener, which updates its public-read field.

    This is the most public-read field and can already be tied from the rest of the JavaFX code.

    Example:

    The listener interface (Listener.java):
    public interface Listener {
    	void notifyChanged (int newValue);
    }
    


    Java code (JavaPart.java):
    public class JavaPart {
    	private int observerableValue;
    	public void setObserverableValue (int newValue) {
    		observerableValue = newValue;
    		Listener l = listener;
    		if (l! = null)
    			l.notifyChanged (newValue);
    	}
    	public int getObserverableValue () {
    		return observerableValue;
    	}
    	private Listener listener;
    	public void setListener (Listener l) {
    		listener = l;
    	}
    }
    


    JavaFX adept (JavaPartAdapter.fx):
    public class JavaPartAdapter extends Listener {
    	public-init var javaPart: JavaPart;
    	init {
    		javaPart.setListener (this);
    	}
    	public-read var currentValue: Integer;
    	public override function notifyChanged (newValue: Integer): Void {
    		currentValue = newValue;
    	}
    }
    


    And the JavaFX code that uses the classes above to bind its variable to a variable from the Java class (Main.fx):
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    import javafx.scene.control.Button;
    import javafx.scene.Group;
    def javaPart: JavaPart = new JavaPart ();
    def adapter: JavaPartAdapter = JavaPartAdapter {
    	javaPart: javaPart;
    };
    Stage {
        width: 200
        height: 300
        scene: Scene {
    	content: [
    	    Button {
    		text: bind "Change {adapter.currentValue}"
    		action: function () {
    			javaPart.setObserverableValue (77);
    		}
    	    }
    	]
        }
    }
    


    This is how it looks at execution. On the left - before pressing the button, and on the right - after. As you can see, calling the setObserverableValue method led to a listener call (JavaPartAdapter), which updated the currentValue variable, which we refer to in the UI.

    BeforeAfter

    Although, at first glance, the technique looks somewhat cumbersome, however, it is uncomplicated in nature and works reliably. Thus, we can assume that a “decent” way to associate JavaFX variables with Java variables exists.

    The following potential hazard should also not be overlooked. In JavaFX, it is possible to get a "memory leak" due to inattentive handling of bind. As previously noted, bind actually implicitly creates a listener, i.e. back link appears. If links are not nullified in a timely manner, then listeners may cause unused objects to still be reachable, so that they will not be destroyed during garbage collection. In this scenario, you can get Out Of Memory.

    For instance:
    class A {
            var a: Integer;
    }
    class B {
            var b: Integer;
    }
    def b: B = B {};
    for (i in [1..100000]) {
            var a: A = A {
                    a: bind bb
            };
    }
    


    Here, we implicitly pass references to the created instances of class A to the variable b of class B. The variable b forms a long long list of variables dependent on it, so instances of class A cannot be thrown out during garbage collection, since they are still reachable .

    cy6ergn0m @ cgmachine test / fx / oom $ javafx -Xmx16m Main
    java.lang.OutOfMemoryError: Java heap space
            at Main.javafx $ run $ (Main.fx: 11)
    cy6ergn0m @ cgmachine test / fx / oom $ 
    


    But it is worth commenting out the line a: bind bb, as the program runs successfully, since all created instances of class A can be easily released.

    If instead, write something like this:
    class A {
            var a: Integer;
    }
    class B {
            var b: Integer;
    }
    var b: B = B {};
    for (i in [1..100000]) {
            A {
                    a: bind bb
            };
            b = B {};
    }
    


    Then it will be done, although not with a bang (rather slowly).
    Unfortunately, in the general case there is no way to remove bind. However, in many cases, you can find a working workaround, the essence of which will already depend on the specifics. In general, you can give advice not to forget that bind adds a listener (a), so that when you change the value (bb), you can find all the dependents (all instances of A in my example) and notify them of the need to recalculate their value (aa) .

    With this, my story about using bind in JavaFX comes to an end. I take off my hat to those brave courageous people who have read this to the end and wish them good luck in mastering this remarkable in essence technology, JavaFX.

    Also popular now: