geometrikal
geometrikal

Reputation: 3294

Cleaning up bindings and change listeners on nested properties when parent properties change in javafx

I have a model class with SimpleXXXXProperty properties. Javafx GUI elements are updated using either bindings or change listeners, e.g.

textField.textProperty().bind(myModel.myModelStatus());

or

myModel.myModelStatus().addListener((obj,oldv.newv) -> { update here });

When the instance of the model class changes, I rebind the controls and add the listeners again. However, I can see by the memory use that the old model still persists in memory.

What do I have to do to remove all references to the model so it can be cleaned up?

Is there are more automatic way of updating bindings and listeners on nested properties when the parent property changes?

Upvotes: 1

Views: 1696

Answers (1)

isnot2bad
isnot2bad

Reputation: 24454

Points to consider when you want to undo bindings (including listeners) to your model:

  • Undirectional bindings (p1.bind(p2)) are automatically unbound when binding the same property again (e.g. p1.bind(p3)), but it does not hurt to do it explicitely (p1.unbind()).
  • Bidirectional bindings (p1.bindBidirectional(p2) or Bindings.bindBidirectional(p1, p2)) have to be unbound explicitely (p1.unbindBidirectional(p2) or Bindings.unbindBidirectional(p1, p2)).
  • Listeners must be unregistered (prop.removeListener(l)).

The third is the tricky part, as listeners are often implemented as lambda expressions or method references. Unfortunately, lambda expressions as well as method references(!) are not constant:

// lambdas are not constant
InvalidationListener l1 = obs -> {};
InvalidationListener l2 = obs -> {};

assert l1 != l2; // they are NOT identical!

Well, this might be obvious for lambdas, but the same is also true for method references, which is really annoying:

// method references are not constant
Runnable runnable1 = this::anyMethod;
Runnable runnable2 = this::anyMethod;

assert runnable1 != runnable2; // they are NOT identical!

That means, you cannot register a lambda expression or a simple method reference as listener if you want to be able to unregister it:

// if you register listeners on a property like that...
label.textProperty().addListener(obs -> System.out.println(obs));
label.textProperty().addListener(this::handleLabelInvalid);

// ...these calls WON'T remove them due to the inequality shown above!
label.textProperty().removeListener(obs -> System.out.println(obs));
label.textProperty().removeListener(this::handleLabelInvalid);

Solution

You have to store a reference to the lambda expression or method referency by yourself. I use to use final fields for that:

public class MyClass {
    // store references for adding/removal
    private final InvalidationListener l1 = this::handleLabelInvalid;
    private final InvalidationListener l2 = obs -> System.out.println(obs);

    ...

    public void bind() {
        label.textProperty().addListener(l1);
        label.textProperty().addListener(l2);
    }

    public void unbind() {
        label.textProperty().removeListener(l1);
        label.textProperty().removeListener(l2);
    }

    private void handleLabelInvalid(Observable observable) { ... }
}

Upvotes: 5

Related Questions