One of the most common issues that raises when developing a desktop application with JavaFX is "How can I pass this user input from scene A to scene B?", while you can always rely on text files or even databases, these may not be the most suitable approach.
The goal of this tutorial is to teach you three different ways you can pass information from one scene to another. Before we dive deep on that, let's first make a few assumptions:
- We have a JavaFX app with two fxml files, SceneA and SceneB, each with its own controller, and we want to pass data from A to B.
- We have a class that encapsulates the information we want. For the sake of this post, let's say this class is defined as follows:
- We have a method named
sendData
in the SceneAController where we gather the information. This method is called upon amouseClicked
event - We have a method named
receiveData
in the SceneBController where we populate an instance of the user class with the information we receive. This method is called upon amouseClicked
event
That's all 👍! Now about those methods...
First Method: Using the UserData function
In JavaFX, all the classes that inherit from the class Node have the following methods: setUserData(Object)
and getUserData()
. According to the official documentation, these methods allow us to attach an arbitrary object to a node for later use. I think you can see how powerful is that! Although we can use any node for this purpose, I recommend you use the stage itself, since it's the same no matter the scene.
In order to use this method, in the sendData
function, follow these steps:
- Save the information in an instance of the user class
- Grab the node representing the button from the
event
object - Get the instance of the stage from the node and close it
- Load the scene through the
FXMLLoader
class - Pass the information to the stage using the
setUserData
function - Create a new scene and pass it to the stage
- Make the stage visible again
The sendData
function should look something like this:
In the receiveData
function the only thing we have to do is:
- Get the instance of the stage just like before
- Recover the object using the
getUserData
function - Use the information as you see fit
Hence, the receiveData
function should look something like this:
And just like that we have managed to pass information between the two scenes, ¡Bravo 🎉!. This approach is very useful when we need a quick way to send data back and forth but, what if we need to pass more than one object? We will see how to overcome this in the next section.
Second Method: Manually Setting the Controllers
Usually when we create our fxml files, the IDE automatically creates a controller for it and link them together using the following property in the root tag of the fxml file:
fx:controller="<package>.Controller"
In addition, this also creates a new instance of the controller class, but if we want to pass information to a field stored in the controller, like we would in a regular class, we must create the instance manually.
In order to use this method, we need to follow these steps:
- Remove the property we saw earlier from the
SceneB.fxml
file - In the SceneBController, create a new attribute of type user along with its
set
andget
function
Back in the sendData
function:
- Follow the first three steps we saw in the previous section
- Create an
FXMLLoader
object and store in it the result of the statementFXMLLoader.load(...)
- Create a new instance of the SceneBController class and pass it the information using the
set
method you created - Use the
setController
method in theFXMLLoader
object you created and pass it the controller instance - Use the
load
method and store its result in theroot
object, the rest is the same as before
The sendData
function should look something like this:
Now in the receiveData
function you can display the information stored in the user field, like this:
This method sure is more complex than the last one, but now you can pass as much objects as you like between the two scenes.
Third Method: Using a Singleton Class
Until now, we have relied on the controllers for passing the information, but if we truly want to mimic the business logic we would do if we were using a database for example, we can create a third class from which the two controllers can access. In order to do that, the controllers must share the same instance of the new class, and we can accomplish that using the Singleton pattern.
There are several ways we can implement this pattern, but the most simple one is by doing the following:
- Create a class and make its constructor private, so new instances can't be created
- Create a constant of the same type as the class and initialize it
- Create a public static function to retrieve said constant
In addition to these steps, you also need to create a field of the class type you are interested in, in this case is user, along with its set
and get
functions. For the sake of this tutorial, let's say we create a class named UserHolder
and follow the steps, this would be the result:
Back in the sendData
function:
- Follow the first 4 steps of the first section
- Grab the static instance of the UserHolder class
- Pass the information using the
set
function you created - The rest is the same as before
And lastly in the receiveData
function, you just need to recover the object from the static instance, like this:
And with a very few steps, we have managed to share information between the scenes through a singleton class. This approach is particularly useful when doing a configuration view, you can store in the singleton class the user's preferences and easily recover them anywhere in your program.
Final Thoughts
In this post, we learned how to pass information from a scene A to a scene B in three different ways, and we saw how we would implement this in our code as well as some use case where you might need them. I hope you have found this useful and want to thank you for your time.
Until next time 👋!
Top comments (7)
This was well written and explained, thanks @devtony101
The step 2 on "Manually creating the controller" method section should be to create a new instance of FXMLLoader not calling FXMLLoader.load
Right, I can see why the title can be misleading, I will update it. Thank you for your feedback!
I am new to javafx. I have a main controller and child controller, each having its own fxml file. The child fxml is displayed via a button on the main view, and it shows and waits till a button on it is clicked to submit data, then it closes; but the main one still exists. I am using a different stage for each, and I suppose it is why the first method does not work for my case. How would I have it work.
The logic is that once the button on the child view is clicked, the date entered in its inputs is displayed on the main view. How do I achieve this...
I completely new to JavaFX and Java as well. Just for a thought, rather than going through any of this, why don't we have parent class for all the controller classes that we have in the application.
Is there anything problematic in doing that?
Getting missing return type error in the last method. Perhaps you meant
public static UserHolder getInstance()
?yup i think it is. Worked for me by doing that