edvin / fxlauncher

Auto updating launcher for JavaFX Applications
Apache License 2.0
713 stars 110 forks source link

Classloader issue when loading FXML files #72

Closed HossamMohsen7 closed 7 years ago

HossamMohsen7 commented 7 years ago

Hello again :)

So I've tried to load my .FXML files from the class path using CLASSNAME.class.getResoure("/RESOURCENSME.fxml") but it doesn't work and it gives me a javafx.fxml.LoadException when starting the launcher. I've tried to use FXMLLoader.getDefaultClassLoader().getResource("RESOURCENAME.fxml") (without the slash) but it still doesn't work and gives me javafx.fxml.LoadException: file:/JAR_FILE_LOCATION!/RESOURCENSME.fxml EDIT: The "!" mark is actually there and not written by me.

Thanks

edvin commented 7 years ago

To load resources within your application manually you should use the ResourceLookup helper.

To get a resource from your classpath, in the same package as the current component:

val myFxmlFile = resources.url("resourcename.fxml") 

Alternatively, from any given location on the classpath:

val myFxmlFile = resources.url("/my/package/resourcename.fxml") 

But why are you loading FXML files manually? You should make a View that represents the FXML file and inject the root of the View from FXML instead:

class MyView : View() {
    override val root: HBox by fxml()

This will look for MyView.fxml in the same package as the View class. Alternatively, give any location as a parameter to the fxml delegate:

class MyView : View() {
    override val root: HBox by fxml("/views/myview.fxml")

Remember also that Maven doesn't put fxml-files in your source-folder on the build path, so you should make sure to put them in src/main/resources and the corresponding package.

If this doesn't solve your problem, please post some code :)

HossamMohsen7 commented 7 years ago

Thanks for the help but I'm sorry I don't recognize this code I'm just using pure java and javaFX to make a desktop game and I'm loading FXML files for the menu and login screens and that things. I have my .FXML files already in my src/main/resources folder and It works great in eclipse when I use CLASSNAME.class.getResoure("/RESOURCENAME.fxml").

What I do is I have a class named SceneChanger that loads all the .fxml files at startup and just changes the Stage parent. EDIT: This is how I load my .fxml files:

SceneChanger.class.getResource("Menu.fxml")

I hope you understood me.

edvin commented 7 years ago

Sorry, I thought you were using TornadoFX for some reason :) I've included an example of loading an FXML file in the FX Launcher Demo Project. This is how I load the FXML file there:

FXMLLoader.load(getClass().getResource("/views/MyView.fxml"))

Can you try using getClass().getResource() like I do there?

Here is the complete method:

https://github.com/edvin/fxldemo/blob/master/src/main/java/no/tornado/FxlDemo.java#L58-L69

If this doesn't solve your issue, can you link to your code?

HossamMohsen7 commented 7 years ago

Sorry it's still giving me the exception. Here's some code:

start method in Main.java:

        @Override
    public void start(Stage primaryStage) {
        System.out.println("Main.start()");
        try {
            primaryStage.setAlwaysOnTop(true);
            primaryStage.setAlwaysOnTop(false);
            primaryStage.getIcons().addAll(
                    new Image("/icons/icon_256.png"),
                    new Image("/icons/icon_128.png"),
                    new Image("/icons/icon_64.png"),
                    new Image("/icons/icon_32.png"),
                    new Image("/icons/icon_16.png"),
                    new Image("/icons/icon_8.png"));

            SceneChanger.loginRoot = FXMLLoader.load(getClass().getResource("/Login.fxml"));

            SceneChanger.scene = new Scene(SceneChanger.loginRoot);

            primaryStage.setScene(SceneChanger.scene);
            primaryStage.show();
            primaryStage.sizeToScene();

            primaryStage.setMaximized(true);

            Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
            primaryStage.setX((primScreenBounds.getWidth() - primaryStage.getWidth()) / 2);
            primaryStage.setY((primScreenBounds.getHeight() - primaryStage.getHeight()) / 2);

            Main.primaryStage = primaryStage;
            new SceneChanger().load();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

Here's SceneChanger().load():

public void load() {
        System.out.println("Loading scenes");
        if (menuRoot == null)
            try {
                menuRoot = FXMLLoader.load(getClass().getResource("/Menu.fxml"));
            } catch (Exception e) {
                e.printStackTrace();
            }

        if (settingsRoot == null)
            try {
                settingsRoot = FXMLLoader.load(getClass().getResource("/Settings.fxml"));
            } catch (Exception e) {
                e.printStackTrace();
            }

        if (lobbyRoot == null)
            try {
                lobbyRoot = FXMLLoader.load(getClass().getResource("/Lobby.fxml"));
            } catch (Exception e) {
                e.printStackTrace();
            }

        if (gameRoot == null)
            try {
                gameRoot = FXMLLoader.load(getClass().getResource("/Game.fxml"));
            } catch (Exception e) {
                e.printStackTrace();
            }

        if (resultsRoot == null)
            try {
                resultsRoot = FXMLLoader.load(getClass().getResource("/Results.fxml"));
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

And I change the view by calling SceneChanger.change which takes a javafx.scene.Parent Here it is: public static void change(Parent root) { scene.setRoot(root); currentRoot = root; }

Also every field and method in SceneChanger.java is static except the load() method.

This code works fine in eclipse but it doesn't work with FXLauncher

HossamMohsen7 commented 7 years ago

Also the .FXML files are not in any package or subpackage

edvin commented 7 years ago

Can you send me the jar of your application or point me to a public repo?

HossamMohsen7 commented 7 years ago

Sorry but the application is private for now I can't give it to you

HossamMohsen7 commented 7 years ago

I'm going to try to put the fxml files into a subpackage to see if it works

HossamMohsen7 commented 7 years ago

Didn't work, but its weird that there is a "!" in the exception: javafx.fxml.LoadException:file:/C:/Users/USERNAME/AppData/Local/HosCraft/Words/Words.jar!/views/Login.fxml

Is that normal?

edvin commented 7 years ago

That's completely normal, it just shows you that it's looking for the file inside Words.jar. If you can't share the app, please make a new app with the absolute minimum content needed to show the behavior.

HossamMohsen7 commented 7 years ago

Ok I will do it right now

HossamMohsen7 commented 7 years ago

Ok it seems like github doesn't like .zip files so I uploaded the files to dropbox: https://www.dropbox.com/s/gy6uvwrkm2v2h38/TestProjectSourcesAndJar.zip?dl=0

The archive containes the source code and a test jar. It works fine with the jar but the problem is when I try to run it with FXLauncher it doesn't work. If you want I'll try to make a launcher using fxlauncher with that file.

HossamMohsen7 commented 7 years ago

OK so I've tried to make a launcher for this app and it worked just fine with no problems. But why doesn't it work with the original app?

edvin commented 7 years ago

You're doing something different in your app. I would take a copy of your original app, reduce features until it looks like the sample that does work, and that should tell you where the problem lies. Hard to help when I can't see the code :)

HossamMohsen7 commented 7 years ago

Ok finally I've got the problem. So what I think FXLauncher does is it loads the start() method from the main class and just skips the main method. What I was doing is I was setting up some initial stuff in the main method but FXLauncher skips them so they are always null Then in my LoginController I have an initialize method that uses some stuff that I have initialized in the main method but in this case it finds them as null so it doesn't work

What can I do or is this a fxlauncher problem?

HossamMohsen7 commented 7 years ago

Oh and if you asked how did I found out this, I just removed some stuff from the initalize method in my LoginController and it worked fine

edvin commented 7 years ago

This is not an FXLauncher problem at all, but you need to realize that when you run with FXLauncher, you start your JavaFX application class, not a main class. The main class is not needed for JavaFX anymore either, allthough it's possible to launch it from there. In the early days of JavaFX you actually had to have a main method, but that's not the case anymore.

The correct way to deal with initialization tasks is to override the init method of your Application class. It is run once the Application class is loaded, and even before the JavaFX Application Thread is created.

HossamMohsen7 commented 7 years ago

Now I get it, Thank you :) But if it doesn't have to have a main method, how am I going to test run the app in eclipse or any IDE?

edvin commented 7 years ago

The IDE and the JVM for that matter knows how to run a JavaFX application even without a main method :) There is of course no harm in having it there, but move your initialization code to the init method and you're golden no matter how to start the app.

Let me know how it goes, I'm pretty sure even Eclipse supports to run a JavaFX App without a main method these days :)

HossamMohsen7 commented 7 years ago

Thanks you every thing is working great now! :) Thanks for the help and thanks for your time :D

edvin commented 7 years ago

Great! Thanks for bringing this up, I'm sure this will be an interesting read for others as well :)

hemindesai04 commented 6 years ago

I had a similar issue, and I found the fix by explicitly setting the class loader using the setClassLoader() method of FXMLLoader class. I was debugging the load() method and found out that the it was returning null when the method to get the class loader was called.