Issue
I am building a JavaFX application and I am wondering if there is a recommendation (best practice) on how to load new Scene
in current Stage
as fast as possible.
Currently what I am doing is (more or less) this:
Parent root = (Parent)myFXLoader.load();
currentStage.setScene(new Scene (root);
The above works fine and fast enough for simple Scene
s BUT when loading more complicated scenes that initialize TableView
s, Combobox
es etc the transition between Scene
s takes many seconds which is annoying.
All the initialization I do in inside the Controller
's initialize(URL url, ResourceBundle rb)
method.
There I add the items to the Choice/Combo
boxes, initialize TableView
etc but as I said, it takes too much time.
Am i doing something wrong? Should I initialize somewhere else?
Thank you.
EDIT:
Anyone interested in helping with this, or even get ideas for their project, I have uploaded a part of my project (Netbeans project) at google.com.
You can check it out using SVN. This is the link:
http://tabularasafx.googlecode.com/svn/trunk/
userName: tabularasafx-read-only
no password required
Instructions after you run the project:
First screen is login screen, just click OK
Second screen is "homePage", there you can see a treeView menu and navigate to 4 different screens
My issue is the loading time of classes->create page. Take a look at it and let me know if you find anything
EDIT:
I made 3 changes that @jewelsea suggested.
1. I used a HashMap to keep all the controllers for each screen
2. I update only part of the Scene and not the whole Scene
3. I used the answer of JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically to help the controllers load faster as described in the answer.
Everything is much much faster now!!!!
Feel free to use the project as a guideline
Also I update the program to navigate through 3 screens for better understanding
note that my code is a messy
Solution
Some Background
I took a look at your project Dimitris.
I timed your load creation time (Java 8 b129 on OS X 10.9, 2012 Macbook Air) for the "classes create" page. It took just over a second for me.
To simplify testing I removed the section where you load new FXML using a concurrent service and just loaded the FXML directly on the JavaFX application thread when it was requested - its a lot easier to work with that way.
Sorry for the long answer here. Things like this usually don't fit well into StackOverflow, they end up best in a tutorial or blog kind of form, but I was curious what was going on, so I thought I'd take some time to look into it and write it up.
Don't create a new Scene for every FXML you load
You set a new scene (with a new size) every time you load the FXML. For whatever reason, this is a pretty expensive operation and you don't need to do it. You already have a scene in your stage, just reuse that. So replace the following code:
stage.setScene(new Scene(service.getValue().getRoot(), service.getValue().getX(), service.getValue().getY()));
with:
stage.getScene().setRoot(service.getValue().getRoot());
This will save just over half a second on the load time, so now classes->create takes about 400 milliseconds the first time it is run.
This change is an example of an easy performance win.
It also provides a nicer user experience as on my machine the stage flashed gray while you were changing scenes, but when you just replace the scene root of an existing scene, there was no gray flash.
Because the JVM runs with a just in time compiler for Java, subsequent requests to display classes->create go faster, so after opening the scene two or three times it takes about 250ms (or quarter of a second).
The FXMLLoader is slow
Of the remaining 250ms to load, about 2ms is spent in your initialization code, another 2ms is spent by JavaFX rendering the controls and the other 246ms are spent by the FXMLLoader loading up the FXML and instantiating the nodes to go into your scene.
The idea with UI code is you want to get the target time for a transition down to < 16 to 30ms. That will make the transition quick and smooth for the user.
Separate your UI code from your Network and Database Code
Network and database calls are things which are best done off of the JavaFX application thread, so you can use the JavaFX concurrency tools to wrap those tasks. But I'd recommend separating concerns. Use concurrent services to fetch data, but once you have the data back, use Platform.runLater or a Task return value to transfer the data the JavaFX application thread and run the population on the JavaFX application thread (because that population task is going to be pretty quick anyway).
This way you have compartmentalized the multithreading in the system to different logical components - networking runs on its own thread and UI operations run on a different thread. It makes stuff easier to reason about and design. Think of it a bit like web programming, where an ajax call fetches data concurrently to the UI, then provides a callback that is invoked to process the data into the UI.
The other reason to do this is that many networking libraries come with their own threading implementations anyway, so you just use that rather than spawning your own threads.
How to make FXML Load Quicker
You shouldn't really need multi-threaded code for loading FXML files. The initialize function of your FXML runs extremely quickly (just a couple of milliseconds). The FXMLLoader takes 250ms. I haven't profiled it in detail to see why that is the case. But there are some indications in Sebastian's answer to JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically. I think the main performance issue is that the FXMLLoader relies so heavily on reflection.
So the best solution in situations where a slow FXMLLoader is an issue would be to use some alternative to the FXMLLoader which performs better and doesn't rely on reflection. I believe the JavaFX team are working on a binary equivalent of the FXMLLoader (e.g. the FXML files are pre-parsed in the build stage into binary Java class files which can be quickly loaded into the JVM). But that work (if it exists) is not released by the JavaFX team yet. A similar piece of work has been done by Tom Schindl, which pre-compiles the FXML to Java source, which can then be compiled to Java classes, so again your app is just working with compiled classes, which should be nice and speedy.
So the solutions to make FXML load quicker are currently in the works, but not really stable and usable on a production system. So you need other ways to deal with this issue.
Make your forms simpler
This may seem like a cop-out on my part, but IMO the design you have for your "create classes" scene is a bit complicated. You might want to consider replacing it with a multi-stage wizard. Such a wizard will generally load faster as you only need to load a handful of items on each wizard screen. But the more important point is that such a wizard is probably easier to use and a better design for your users.
Replace only the sections of your scene that you need to
You are loading FXML files which create your whole application UI for each new page. But you don't need to do this because things like the top menu, status bar and navigation sidebar don't change just because the user loads a new form - only the central section where the "create classes" form is displayed is changing. So just load up the nodes for the part of the scene that is changing rather than the entire scene contents.
Additionally this will help fix other issues that you will have with your application by replacing the whole UI at each stage. When you replace the navigation menu, the menu doesn't automatically remember and highlight the currently selected item in the navigation tree - you have to go and explicitly remember it and reset it again after doing a navigation. But if you weren't replacing the whole scene contents, the navigation menu would remember what was last selected and display it (because the navigation menu itself isn't changing on navigation).
Cache FXML load node trees and controllers
You are only ever displaying a single "create classes" form at a time within the application. So you only need to use the FXMLLoader to load the "create classes" form once. That will create a tree of nodes for the form. Define a static HashMap that maps "create classes" to the CreateClassesController object (of which you also have only one in the application). When you navigate to the "create classes" screen, see if you have already been there before, by retrieving the controller from your hash map. If there is already an existing controller class, query it to get the root pane for form and display the form in your scene by replacing the center panel of your scene with the new form. You can add extra methods on the controller that you can call to clear any existing data values in the form or to set any data values which you have loaded from a network fetching task.
In addition to speeding up your application, you now have the advantage that the state of the "create classes" form is kept until you or the user decide to clear it. This means that the user can go through and partially fill out the form go somewhere else in the application then return to the form and it will be in the same state as they left it rather than forgetting everything the user entered before.
Now because you load the "create classes" form only once, you could load up all of the forms at startup (and have a preloader page which indicates that your application is initializing). This means that initial startup of the app will be slower, but operation of the app will be quick.
Suggested Design
- Create forms for different panel sections in your app (nav bar, "create class" form, "home screen", etc).
- Create and manipulate UI elements only on the JavaFX UI thread.
- Only replace panel sections on navigation, not entire scenes.
- Precompile FXML to class files.
- Use a Splash Screen pre-loader if necessary.
- Abstract networking and data fetching code into its own thread.
- Reuse cached node trees created for panel forms rather than recreating them.
- When new network data is available, transfer it to the UI thread and fill it into a cached node tree.
Review the SceneBuilder Implementation
Follow the principles used the SceneBuilder implementation itself - it is the best current design example for a reasonably sized JavaFX project that makes use of FXML for its UI. The SceneBuilder code is open source and distributed under a BSD style license, so its good to study.
Outcome
I prototyped some of the ideas mentioned in this answer and this cut the initial load time of the "create classes" screen down from over a second to about 400ms (for the first time the screen is loaded). I didn't replace the FXMLLoader with something else, (which I am sure would have greatly decreased the 400ms value). Subsequent loads of the "create classes" form based a cached node tree that was just re-added to the scene took about 4ms - so operational performance was instantaneous as far as the user was concerned.
Update for Additional Questions
Do you think that I should use Tom Schindl's solution for compiling FXML or is it "too Beta"?
My guess is that (as of today) it is "too Beta". But try it out for yourself and see if it meets your needs. For support on the Tom's FXML => JavaFX compiler, post to the e(fx)clipse forums, as the project falls under the larger umbrella of the e(fx)clipse project.
And I tried 'stage.getScene().setRoot(service.getValue().getRoot());' but got OutOfMemoryError: Java heap space do you think that line caused it or it is not relevant?
I was doing some profiling of your code as part of creating this answer (by attaching the NetBeans profiler to an already running instance of your application). I did notice that every time the "create class" scene was loaded by your program, the memory usage would grow quite significantly and the memory did not seem to be released. I didn't spend time trying to track down what the reason for that was, but that was profiling your code unmodified. So I suspect that the ultimate cause of the system running out of memory is not to do with whether you swap out a scene or just swap out a scene root. I noticed a lot of memory was consumed by CSS psuedo-classes, though I couldn't tell you the reason for that. My guess is that if you follow the principles outlined in this answer, then overall, your application will be a lot more efficient and you may circumvent the memory related issues present in your current code. If not, you could continue to profile the application memory usage to see what the root issues are.
Answered By - jewelsea
Answer Checked By - Senaida (JavaFixing Volunteer)