3

I have a control that should react to some kinds of events, and therefore adds itself to a listener list in the constructor. Of course it should remove itself as well, but while Swing and RCP have dispose() methods, I can't seem to find any in JavaFx.

public class MyTable extends TableView implements MyListener {

public MyTable () {
    Events.addListener(this);
}

public disposeOrSomething() {
    Events.removeListener(this);
}

// actual implementation of MyListener 

}

Don't get hung up on the static Events class, thought. It does not matter to me if the control adds itself to something static or not. What is important: there are two parts of an application that are not connected except for a general event system. So when the listening part vanishes (e.g. the window gets closed) of course the listener should be disposed.

Up until know, when the listener was directly connected to the life cycle of the control, the control registered it. But now in JavaFX that won't work, and due to loosely coupled FXML files I can't see anyone else who has the necessary information (the control and the "window close event") either.

So what is a good way to clean up after controls when they are no longer used?

Stefan S.
  • 3,950
  • 5
  • 25
  • 77
  • AFAIK, there's no built-in `dispose()` (or equivalent) method in JavaFX – Spotted Sep 16 '15 at 06:01
  • In Swing, what "dispose"-method are you referring to? – Puce Sep 16 '15 at 10:57
  • @Puce I don't see how that adds to the question, but all the windows have a `dispose()` method. – Stefan S. Sep 16 '15 at 11:00
  • Please elaborate what this static method Events.addListener does. – Puce Sep 16 '15 at 11:00
  • 2
    Consider using `WeakListener`s, e.g. a [`WeakChangeListener`](http://docs.oracle.com/javase/8/javafx/api/javafx/beans/value/WeakChangeListener.html). That way the listener will not prevent garbage collection of your object. This design just feels wrong, though. – James_D Sep 16 '15 at 12:03
  • @Puce I added the info. – Stefan S. Sep 16 '15 at 12:21
  • @SteffiS. I would strongly advise not using a global event bus. Anyway, I can give you a workaround if you provide the signature of `MyListener` interface. – Tomas Mikula Sep 17 '15 at 16:59

2 Answers2

1

As said before, I recommend not using a global event bus and, as suggested by @Puce, not registering listeners on anything else than encapsulated objects.

Anyway, here is a way to automatically subscribe to events only when a certain Node is showing (i.e. automatically unsubscribe when the node is removed from the scene or the window is closed). This solution makes use of ReactFX, namely the helper EventStream#conditionOnShowing method.

Since you didn't provide the signature of MyListener, I will assume this signature:

@FunctionalInterface
interface MyListener {
    void handle(MyEvent event);
}

import org.reactfx.EventStream;
import org.reactfx.EventStreamBase;
import org.reactfx.Subscription;

public class MyTable extends TableView {
    public MyTable () {

        // Since you are using some custom event dispatching mechanism,
        // the first step is to convert that into a ReactFX EventStream.
        EventStream<MyEvent> events = new EventStreamBase<MyEvent>() {
            @Override protected Subscription observeInputs() {
                MyListener l = event -> notifyObservers(event);
                Events.addListener(l);
                return () -> Events.removeListener(l);
            }
        };

        // Now the interesting part: events will not be observed whenever
        // this MyTable is not part of a showing Window.
        events.conditionOnShowing(this).subscribe(event -> this.handleEvent(Event));
    }

    private void handleEvent(MyEvent) {
        /* ... */
    }
}
Tomas Mikula
  • 6,537
  • 25
  • 39
-1

I recommend to use the Mediator pattern and only listen to events of own child controls.

Puce
  • 37,247
  • 13
  • 80
  • 152