2

Hello dear Stackoverflow,

I have several questions regarding the JavaFX class Preloader. After a long-term research, I haven't found any proper solution to my problem and I think it's not really the issue with my code, but rather the limits of JavaFX itself when it comes to the Preloader class.

Let me get straight up to my point: Is it not possible to use FXML to define the design of a Preloader? It's interesting to see how all the tutorials only guide you throughout creating a preloader by creating new Instances out of the classes. (ProgressBar progressBar = new ProgressBar(); ie)

So, let's take this example code for you to test this with me:

TestPreloader:

import com.sun.javafx.application.LauncherImpl;
import javafx.application.Preloader;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class TestPreloader extends Preloader {


    @FXML
    ProgressBar progressBar;

    public static void main(String[] args) {
        LauncherImpl.launchApplication(TestApplication.class, TestPreloader.class, args);
    }

    public void initialize() {
        System.out.println("initialize printed");
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.initStyle(StageStyle.TRANSPARENT);
        Parent root = FXMLLoader.load(getClass().getResource("preloader.fxml"));
        Scene scene = new Scene(root);
        scene.setFill(Color.TRANSPARENT);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        primaryStage.centerOnScreen();
        primaryStage.show();
    }

    @Override
    public void handleProgressNotification(ProgressNotification progressNotification) {
        // progressBar.setProgress(progressNotification.getProgress());
    }

    @Override
    public void handleStateChangeNotification(StateChangeNotification notification) {

        System.out.println(notification.getType().toString());
        switch (notification.getType()) {
            case BEFORE_START:
                //progressBar.setProgress(1);
                break;
            case BEFORE_LOAD: //this is where the TestApplication class will be constructed
                //progressBar.setProgress(1);
                break;

            case BEFORE_INIT:
                //progressBar.setProgress(1);
                break;
        }

    }
}

FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>


<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="TestPreloader">
       <children>
          <ProgressBar fx:id="progressBar" layoutX="174.0" layoutY="141.0" prefWidth="200.0" progress="0.0" />
       </children>
    </AnchorPane>

TestApplication

import javafx.application.Application;
import javafx.stage.Stage;


public class TestApplication extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        System.out.println("Application started!");
    }
}

The output if you start is in the following order:

initialize printed
BEFORE_LOAD
BEFORE_INIT
BEFORE_START
Application started!

now, as you can see the initialize() method is being called firsthand, then the state of the preloader changes and thus upon every change the notification pops up - all good until now.

Yet what bothers me is, if I call progressBar#setProgress IN the initialize method, the progress actually gets set to 1 and no errors are being thrown. Although the States occur AFTER the Preloader is initialized, NullPointers occur.

To understand what I mean, uncomment setProgress under handleStateNotification (under the BEFORE_LOAD statement would make the most sense to me), and you'll see that a NullPointerException pops up.

Now, why does it occur? What can I do to fix this issue? It just doesn't make any sense for me. If the Preloader is initialized and the ProgresssBar is not null, how can it turn null afterwards? Perhaps maybe there is a new instance of the own class being used separately? But that wouldn't make sense as well to me. Why would you have that?

I'm stuck on this problem for a few days now, I mean I could just go for a Timeline and "hardcode" it, but well, I want to learn what causes these issues.

TLDR; ProgressBar turns null after initializing the application when using the Preloader class, what causes it and what should I do?

EDIT: It seems like the class is not being reconstructed somewhere, idk i'm really stuck lol

Sh0ck
  • 97
  • 2
  • 12
  • Did you paste all of your FXML file? I couldn't see a ProgressBar inside it. – Emre Dalkiran Aug 11 '18 at 16:40
  • @EmreDalkiran updated it, sorry, was a formatting issue lol – Sh0ck Aug 11 '18 at 16:41
  • Unrelated to question: Why are you using `LauncherImpl` in you `main` method? It is an internal (`com.sun.*`) method. The public method is `javafx.application.Application.launch`. – Slaw Aug 11 '18 at 17:16
  • Duh, had that from a tutorial and didn't want to bother looking into a more efficient way until I had the Preloader running. Thanks for pointing out, though. Edit: I just noticed, that it wont even run the code, if I do it as you stated. – Sh0ck Aug 11 '18 at 20:02
  • Sorry, didn't understand the implications (haven't really used preloaders in standalone applications). In Java 9+ this method will be hidden from you though. See https://stackoverflow.com/questions/47533370/java-9-javafx-preloader for another way. – Slaw Aug 12 '18 at 12:37
  • Ah - thanks for referring me! – Sh0ck Aug 14 '18 at 10:30

1 Answers1

3

When the FXMLLoader loads your FXML file, it creates a new instance of the specified controller. However, this is not the desired outcome. The object that receives your state callbacks should be the controller of your FXML window.

Replacing your FXMLLoader.load(...) block with

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("preloader.fxml"));
fxmlLoader.setController(this);
Parent root = fxmlLoader.load();

and removing the controller property from your FXML, causes the FXMLLoader to use the given controller instance (being the one that receives the state callbacks) instead of it constructing a new instance of the specified controller class.

Limnic
  • 1,826
  • 1
  • 20
  • 45