javajavafxjavafx-9

Passing reference to javafx.application.Application


Consider a non-fx existing application, let's call it Business.

Business exposes a Model object, which in turn exposes some properties. Model also accepts listeners to those properties.

My question is about adding JavaFx gui to such application. The GuiApp obviously extends javafx.application.Application and will need a reference to a Model object.

Searching for a solution for passing a non-String parameter to GuiApp I found several different approaches:

Are there another viable approaches ? Best practice ?


Solution

  • I'll try to demonstrate some different approaches for passing a reference between a java program, and a java-fx program.
    I post it in hope it will help some future readers having similar need. I also hope it may encourage other answers with additional solutions.
    The posted code should not be considered proper implementation, but rather a short code aiming to clarify the different approaches. For this purpose I'll introduce a simple listening interface :

    interface Observe{ void update(int i); }
    

    A java class, that represents an exiting business application :

    public class JavaApp {
    
        private Observe observer;  private int counter = 0;
    
        JavaApp(Observe observer){  //not null safe
            this.observer = observer;
        }
    
        void process() {            
            new Timer().scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    observer.update(counter >=100 ? 0 : ++counter);
                }
            }, 1000,1000);
        }
    }
    

    A java-fx application that should be added to the existing business application, listen to it and serve as view:

    public class JavaFxApp extends Application implements Observe{
    
        private Label label;
    
        @Override public void start(Stage stage) {
            label = new Label("waiting");
            BorderPane pane = new BorderPane(label);
            Scene scene = new Scene(pane, 100, 100);
            stage.setScene(scene);
            stage.show();
        }
    
        @Override public void update(int i) {
            Platform.runLater(()-> label.setText(String.valueOf(i)));
        }
    }
    

    How do we share a reference, in this case a reference to Observe instance, between the two applications ?

    Approach 1: Consider the start() method as the entry point to the application (see James_D answer)
    This is simple and straight forward if you want to tie the existing java application with java-fx and use java-fx Application as the entry point:

    public class JavaFxApp extends Application implements Observe{
    
        private Label label;
    
        @Override public void start(Stage stage) {  
            JavaApp main = new JavaApp(this);
            label = new Label("waiting");
            BorderPane pane = new BorderPane(label);
            Scene scene = new Scene(pane, 100, 100);
            stage.setScene(scene);
            stage.show();
    
            new Thread(()-> { main.process();}).start(); //launch the business process
        }
    
        @Override   public void update(int i) {
            Platform.runLater(()-> label.setText(String.valueOf(i)));
        }
    
        public static void main(String[] args) { launch();  }
    }
    


    Approach 2: Use JavaFX 9 Platform#startup
    This is the best solution I found, when you can not use the Application#start method as the entry point to the application.
    As demonstrated in fabians answer, as off java-fx 9 you can launch without extending Application. All you have to do is modify the main of the java application:

    public class JavaApp {
    
        private Observe observer;  private int counter = 0;
    
        JavaApp(Observe observer){//not null safe
            this.observer = observer;
        }
    
        void process() {
            new Timer().scheduleAtFixedRate(new TimerTask() {
                @Override   public void run() {
                    observer.update(counter >=100 ? 0 : ++counter);
                }
            }, 1000,1000);
        }
    
        public static void main(String[] args) {
            JavaFxApp view = new JavaFxApp(); //initialize JavaFx application
            JavaApp main = new JavaApp(view);
    
            Platform.startup(() -> {//launch JavaFx application 
    
                Stage stage = new Stage();
                try {
                    view.start(stage);
                } catch (Exception ex) {ex.printStackTrace();}
            });
    
            main.process(); //run business process 
        }
    }
    


    Approach 3: Use Static members
    For example introduce a static getter in the java-fx application :

    public class JavaFxApp extends Application {
    
        private static Label label = new Label("waiting");
    
        @Override public void start(Stage stage) {  
            BorderPane pane = new BorderPane(label);
            Scene scene = new Scene(pane, 100, 100);
            stage.setScene(scene);
            stage.show();
        }
    
        static Observe getObserver() {
            return JavaFxApp::update;
        }
    
        private static void update(int i) {
            Platform.runLater(()-> label.setText(String.valueOf(i)));
        }
    }
    

    and use it in the java application:

    public class JavaApp {
    
        private Observe observer;  private int counter = 0;
    
        JavaApp(Observe observer){//not null safe
            this.observer = observer;
        }
    
        void process() {
            new Timer().scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    observer.update(counter >=100 ? 0 : ++counter);
                }
            }, 1000,1000);
        }
    
        public static void main(String[] args){
            new Thread(()-> Application.launch(JavaFxApp.class)).start();
            Observe observer = JavaFxApp.getObserver(); //get static observer reference
            JavaApp main = new JavaApp(observer);
            main.process();
        }
    }
    

    A better approach to get a static reference might be (based on this answer) :

    public class JavaFxApp extends Application implements Observe{
    
        private static final CountDownLatch latch = new CountDownLatch(1);
        private static Observe observer = null;
        private Label label;
    
       @Override public void init() {
           observer = this;
           latch.countDown();
        }
    
        @Override public void start(Stage stage){
            label = new Label("waiting");
            BorderPane pane = new BorderPane(label);
            Scene scene = new Scene(pane, 100, 100);
            stage.setScene(scene);
            stage.show();
        }
    
        @Override public void update(int i) {
            Platform.runLater(()-> label.setText(String.valueOf(i)));
        }
    
        static Observe getObserver() {
            try {
                latch.await();
            } catch (InterruptedException e) { e.printStackTrace();  }
    
            return observer;
        }
    }