el.Quero
el.Quero

Reputation: 25

Binding objects not working outside FXML UI controller initialize() method

Core problem: Binding objects invalidation listeners are not triggered if bound properties are updated outside the initialize() method where they are declared.

Take this initialize() method declared in a JavaFX UI controller class:

@FXML
private void initialize() {
    final StringProperty stringProperty = textField.textProperty();
    stringProperty.addListener(
            (observable, oldValue, newValue) -> System.out.println("stringProperty value: " + newValue));
    Bindings.createStringBinding(() -> "PREFIX - " + stringProperty.getValue(), stringProperty).addListener(
            (observable, oldValue, newValue) -> System.out.println("StringBinding value: " + newValue));

    // Editing stringProperty value inside initialize() method
    stringProperty.setValue("u");
    stringProperty.setValue("ua");
    stringProperty.setValue("ua");
    stringProperty.setValue("uaa");
}

As you can see there I declare a StringBinding that depends on the text property of a TextField, called stringProperty, and a ChangeListener that requests the compute of the StringBinding when it gets invalid.

If I edit stringProperty value inside the initialize method both stringProperty and StringBinding change listeners are triggered, while if I edit the stringProperty value from the UI only the stringBinding change listener is triggered.

Can someone explain me why this happens?

Upvotes: 0

Views: 110

Answers (1)

Slaw
Slaw

Reputation: 46255

Since no strong references to the StringBinding created by Bindings.createStringBinding exist it is eventually being garbage collected. Once that happens, the listener you added will be garbage collected along with it.

I don't think that this is the point because Binding object listen to their dependencies (Observable objects) by an InvalidationListener, and the Obsevable.addListener(InvalidationListener) documentation states that "The Observable stores a strong reference to the listener which will prevent the listener from being garbage collected and may result in a memory leak."

This is true, but take a look at the listener implementation the XXXBinding classes use:

/*
 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.binding;

import java.lang.ref.WeakReference;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakListener;
import javafx.beans.binding.Binding;

public class BindingHelperObserver implements InvalidationListener, WeakListener {

    private final WeakReference<Binding<?>> ref;

    public BindingHelperObserver(Binding<?> binding) {
        if (binding == null) {
            throw new NullPointerException("Binding has to be specified.");
        }
        ref = new WeakReference<Binding<?>>(binding);
    }

    @Override
    public void invalidated(Observable observable) {
        final Binding<?> binding = ref.get();
        if (binding == null) {
            observable.removeListener(this);
        } else {
            binding.invalidate();
        }
    }

    @Override
    public boolean wasGarbageCollected() {
        return ref.get() == null;
    }
}

As you can see, the listener instance that's added to the dependencies (i.e. the Observables) is a WeakListener and only maintains a weak reference to the Binding. This allows the Binding to be garbage collected even if it hasn't been properly disposed. This is done to help prevent a memory leak in the case the Binding has fallen out of scope but the Observables haven't.

In other words, the Observable maintains a strong reference to the InvalidationListener, but the InvalidationListener maintains a weak reference to the Binding.


This sort of behavior is documented in "remote" places, including Property#bind(ObservableValue):

Create a unidirection binding for this Property.

Note that JavaFX has all the bind calls implemented through weak listeners. This means the bound property can be garbage collected and stopped from being updated.

And Binding#dispose():

Signals to the Binding that it will not be used anymore and any references can be removed. A call of this method usually results in the binding stopping to observe its dependencies by unregistering its listener(s). The implementation is optional.

All bindings in our implementation use instances of WeakInvalidationListener, which means usually a binding does not need to be disposed. But if you plan to use your application in environments that do not support WeakReferences you have to dispose unused Bindings to avoid memory leaks.

Note: The implementation doesn't appear to use WeakInvalidationListener, but the effect is the same.

Upvotes: 0

Related Questions