0

In a JavaFX application, javafx.application.Application must be subclassed, and the inherited launch() method, although it's public, must be called from within this derived class, otherwise an exception is thrown. The launch() method then uses reflection to instantiate the derived class, making it difficult to set values for the class members without losing them when launching. All that appears totally unusual to me, and I was wondering why starting a JavaFX application is so complicated, if that kind of software design (design pattern?) has a name, or if it's just bad design?

EDIT:

To be more specific, I want to use the observer pattern, so my java application gets notified when a document was loaded, like this:

public class MyDocumentLoader extends Application
{
    private ChangeListener<Worker.State> changeListener;

    public void setChangeListener(ChangeListener<Worker.State> changeListener)
    {
        this.changeListener = changeListener;
    }

    ...

    public void loadDocument(String url)
    {
        webEngine.getLoadWorker().stateProperty().addListener(changeListener);
        webEngine.load(url);
    }

    ...

}

I need the callback member in several methods, and ideally I can have more than one instances of the class that loads documents, so I can set different ChangeListeners for different URLs.

BinaryGuy
  • 1,246
  • 1
  • 15
  • 29
Stefan Berger
  • 195
  • 14
  • 1
    Perhaps you could provide a more specific example of where this is causing you difficulties, maybe someone would be able to suggest the appropriate refactoring of your code. – James_D Sep 23 '15 at 13:22
  • OK, but this class looks like a highly unlikely candidate for an `Application` subclass. Shouldn't it just be a standalone class that is used by the application? Your `Application` subclass represents the actual application as a whole. – James_D Sep 23 '15 at 14:45
  • Yes, it should be a standalone class to have more than one instance. My intention to ask this question was exactly that I don't want the application to use my class. I want my java application to use JavaFX. My main class somewhere else and changing it to subclass the javafx Application class is completely out of the question. – Stefan Berger Sep 23 '15 at 14:52
  • Anyway, forget the example. All the answers here qualify as the solution, I haven't decided yet. Thanks for the responses. – Stefan Berger Sep 23 '15 at 14:52
  • Well, now your question is less clear. (Perhaps you actually have a different question.) What is your actual existing application, and why can you not refactor it to subclass `Application`? How does the rest of your application relate to the JavaFX part? – James_D Sep 23 '15 at 15:03
  • I have a large software project that collects different kinds of information from several different web service providers. Some of them provide APIs to let me query the information, but others don't. If they don't I can often still connect to their web interfaces and export csv or xml files. So most parts of my application are not at all related to JavaFX, it's merely a workaround. – Stefan Berger Sep 23 '15 at 15:18
  • Even if only part of the app uses FX, you need to start the FX toolkit somehow. Other than ugly hacks with `JFXPanel`, the only way to do that is at startup. Just move the (hopefully few lines of) code from `main` to `init()`. You can completely ignore the `Stage` that's passed to `start()` if you don't want any UI content at the beginning (just make `start()` a no-op), and then have your other processes create `Stage`s via `Platform.runLater(...)` if you need. Really sounds like you are trying to ask something different to what's posted here, in a way. – James_D Sep 23 '15 at 15:26
  • No, we just got lost in details. My question was if the design of javafx, for example to force the main class to be a subclass, follows a known pattern, with a name, and what the reason for the design decision was. Again, thanks for the answers. – Stefan Berger Sep 23 '15 at 15:31
  • OK, fair enough. There is no name for a pattern like that, that I'm aware of. But I'd still encourage you to think about refactoring the application as described, hope it's been some help at least. – James_D Sep 23 '15 at 15:34

2 Answers2

7

My guess is that this design was motivated by the (vast) number of Swing applications that were incorrectly written, with the "primary" JFrames being instantiated and shown on the wrong thread (i.e. not on the AWT event dispatch thread). My guess is that so many Swing applications were incorrectly written that they had to defensively code the framework against the incorrect usage, and that they wanted to avoid this scenario with JavaFX.

Forcing (well, almost forcing, there are hack-arounds) an FX Application to start this way makes it much harder to write an application incorrectly in a similar way. The launch method (and the equivalent Oracle JVM startup process if you have an Application subclass without a main method and a call to launch) does quite a bit of boilerplate work: it starts the FX toolkit, instantiates the Application subclass and calls its init() method, then on the FX Application Thread it instantiates the primary Stage and passes it to the Application subclass's start(...) method. This then ensures everything is running on the correct thread.

You should basically consider the start(...) method in a JavaFX application as the replacement for the main(...) method in a "traditional" Java application, with the understanding it is invoked on the FX Application Thread.

My recommendation is that the Application subclass should be as minimal as possible; it should just delegate to something else to actually create the UI, and then should just place it in the primary stage and show it. Include a main method that does nothing other than call launch(...) as a fallback for non-JavaFX-aware JVMs. You should only have one instance of one Application subclass present in any JVM. This way your Application subclass has no class members to set, and so the issues you describe simply don't arise.

If you use FXML, this is actually fairly natural: the start(...) method essentially just delegates to the FXML-controller pair to do the real work. If you don't use FXML, create a separate class to do the actual layout, etc, and delegate to it. See this related question which gets at the same kind of idea.

Note also that your statement

the inherited launch() method, although it's public, must be called from within this derived class

is not entirely accurate, as there is an overloaded form of the launch(...) method in which you can specify the application subclass. So, if you really need, you can just create a stub for starting the FX toolkit:

public class FXStarter extends Application {

    @Override
    public void start(Stage primaryStage) {
        // no-op
    }
}

Now you can do:

public class MyRegularApplication {

    public static void main(String[] args) {
        // start FX toolkit:
        new Thread(() -> Application.launch(FXStarter.class)).start();
        // other stuff here...
    }
}

Note that launch does not return until the FX toolkit shuts down, so it is imperative to put this call in another thread. This potentially creates race conditions, where you may try to do something needing the FX toolkit before launch(...) has actually initialized it, so you should probably guard against that:

public class FXStarter extends Application {

    private static final CountDownLatch latch = new CountDownLatch(1);

    public static void awaitFXToolkit() throws InterruptedException {
       latch.await();
    }

    @Override
    public void init() {
        latch.countDown();
    }

    @Override
    public void start(Stage primaryStage) {
        // no-op
    }
}

and then

public class MyRegularApplication {

    public static void main(String[] args) throws InterruptedException {
        // start FX toolkit:
        new Thread(() -> Application.launch(FXStarter.class)).start();
        FXStarter.awaitFXToolkit();
        // other stuff here...
    }
}

SSCCE (I just used inner classes for everything so this is convenient to run, but in real life these would be standalone classes):

import java.util.Random;
import java.util.concurrent.CountDownLatch;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class BackgroundProcessDrivenApp {

    public static void main(String[] args) throws InterruptedException {
        Platform.setImplicitExit(false);
        new Thread(() -> Application.launch(FXStarter.class)).start();
        FXStarter.awaitFXToolkit();
        new MockProcessor().doStuff() ;
    }

    public static class FXStarter extends Application {

        private static final CountDownLatch latch = new CountDownLatch(1);

        @Override
        public void init() {
            latch.countDown();
        }

        public static void awaitFXToolkit() throws InterruptedException {
            latch.await();
        }

        @Override
        public void start(Stage primaryStage) { }
    }

    public static class MockProcessor {

        private final int numEvents = 10 ;

        public void doStuff() {
            Random rng = new Random();
            try {
                for (int event = 1 ; event <= numEvents; event++) {
                    // just sleep to mimic waiting for background service...
                    Thread.sleep(rng.nextInt(5000) + 5000);
                    String message = "Event " + event + " occurred" ;
                    Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
                }
            } catch (InterruptedException exc) {
                Thread.currentThread().interrupt();
            } finally {
                Platform.setImplicitExit(true);
            }
        }
    }

    public static class Messager {
        private final String message ;

        public Messager(String message) {
            this.message = message ;
        }

        public void showMessageInNewWindow() {
            Stage stage = new Stage();
            Label label = new Label(message);
            Button button = new Button("OK");
            button.setOnAction(e -> stage.hide());
            VBox root = new VBox(10, label, button);
            root.setAlignment(Pos.CENTER);
            Scene scene = new Scene(root, 350, 120);
            stage.setScene(scene);
            stage.setAlwaysOnTop(true);
            stage.show();
        }
    }
}
Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
2

JavaFX supports a great number of deployment and packaging strategies, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html, and having a standardized lifecycle entry- and exit-point simplifies supporting all these strategies.

If you are struggling to initialize your main application class, due to it being instanciated by the JavaFX launcher, your best option is to use the Application.init() and Application.stop() methods, as James_D points out.

oddbjorn
  • 359
  • 2
  • 11