#Best Practices for managing JavaFX Screens

1 messages · Page 1 of 1 (latest)

feral nimbus
#

I tried a few approaches for managing "screens" from fxml centrally, but none was satisfactory to me.
I tried implementing a dynamic registry, but searching through the classpath was too complicated for me.
I tried having a public static method to load the scene, but that meant a lot of duplicated code for loading fxml and needlessly implemented methods that all do the same.
My internal code quality metrics tell me that thats not a good solution either.

So are there any libraries that i could look at or well known patterns that i could follow?

proper lakeBOT
#

This post has been reserved for your question.

Hey @feral nimbus! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically marked as dormant after 300 minutes of inactivity.

TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.

peak dragon
feral nimbus
proper lakeBOT
peak dragon
celest hearth
peak dragon
celest hearth
#

I am sometimes using an abstract base class for controllers with custom init methods etc and then I more or less have static methods for loading

#

(or in principle code generation is possible as well)

#

but it depends on your exact requirements

peak dragon
#

Years ago (Java 8 was new)I found a very nice tutorial on YouTube for very simple managing screens. It was able to switch one screen into another one. It was also supporting animations between transitions from one screen to another.

But then I started building much more complex applications where I had screens built from other screens (single blocks) and then I realised it is not enough for me.

celest hearth
#

oh that's what you want to do

#

yeah for that you need some sort of classpath scanning or code generation

peak dragon
#

Form with a single button is not enough for business applications...

celest hearth
#

but you can do some very simple classpath scanning for that

#

I assume you are using FXML for all your screens?

peak dragon
#

Yep

celest hearth
#

Is it possible to create one folder (in your resources) where all of these FXML files are contained in and follow a naming convention such that no non-screen FXML files in that folder match the naming convention?

peak dragon
#

Then I started thinking about how to separate business logic from the view. So I had to distinguish between the view controller (bound to a view) and the "form" controller which contains logic+ services for real stuff...

peak dragon
#

Then I have prepared a lot of "standard" predefined views with some minimal logic. So for example a login dialog with validation would be quite easy to create...

celest hearth
#

You should be able to do something like

String folder = "/your/screens";
try(BufferedReader folderEntryReader = new BufferedReader(getClass().getClassLoader().getResourceAsStream(folder)){
  List<String> fxmlResources = folderEntryReader.lines().filter(line -> line.endsWith(".fxml")).map(folderEntry -> folder+"/"+folderEntry).toList();
  // ...
}
peak dragon
#

And for resources, I got inspired by Android and have a plugin which discovers all resources and generates a class with paths to each resource so loading is also easy...

celest hearth
#

and if you want to use some sort of dependency injection for the business logic, that's an option as well

peak dragon
#

I could have resources placed in multiple java/maven modules so I have implemented a "discovery" process to allow load view from any module...

celest hearth
peak dragon
#

Yeah, that's what I'm using 😁

celest hearth
#

you can implement something where classes can register themselves and these classes then know where you can find the FXML files

#

i.e. the classes know about the directories

celest hearth
feral nimbus
celest hearth
#

What I said before still applies

#

Also do you need to certify every single small library you are adding?

feral nimbus
celest hearth
#

You can do simple classpath scanning without external libraries as I've shown before

feral nimbus
#

I've tried this before, but it did not work too good.

celest hearth
#

What was the issue?

feral nimbus
#

I had the problem that my library was inside it's own package and I had a registration step where I would pass a class to my library to search the entire package of that class, but only views contained in the same package like the library were found.

celest hearth
#

if you use .getClass().getClassLoader(), it should search from the classpath root

celest hearth
#

and the same should apply when the path starts with a /

celest hearth
#

by default, it uses the package of the class

#

you can use getClassLoader() or start it with a /

feral nimbus
#

Another weirdly mysterious Java detail 😅

celest hearth
#

it's literally in the docs

feral nimbus
#

I tried with getClassLoader() too, but it did not work as I expected. It showed me files that it wasn't supposed to find and did not find ones I wanted it to find. But maybe my code was shit.

celest hearth
#

Note: Class#getResourceAsStream doesn't work across modules I think

#

and even for ClassLoader#getResourceAsStream, the package should be exported

celest hearth
feral nimbus
celest hearth
#

not exports to but opens

feral nimbus
#

I exported them

#

I'll go and try to whip something up so I can test. But I'd like to not close the thread for now.

celest hearth
#

otherwise if you have access to a Class, you can use the getResourceAsStream method on that Class with a leading / to accesses resources of that module

celest hearth
celest hearth
# feral nimbus Maybe that was the weird part.

Resources in named modules are subject to the encapsulation rules specified by Module.getResourceAsStream. Additionally, and except for the special case where the resource has a name ending with ".class", this method will only find resources in packages of named modules when the package is opened unconditionally (even if the caller of this method is in the same module as the resource).

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ClassLoader.html#getResource(java.lang.String)

#

actually the "opens" is also a requirement with Class#getResourceAsStream

Resources in named modules are subject to the rules for encapsulation specified in the Module getResourceAsStream method and so this method returns null when the resource is a non-".class" resource in a package that is not open to the caller's module.

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Class.html#getResourceAsStream(java.lang.String)

#

but for that, opens to is sufficient