
Topics in Part V
- Persisting data as XML
- Using the JavaFX FileChooser
- Using the JavaFX Menu
- Saving the last opened file path in user preferences
Other Tutorial Parts
- Part I - Scene Builder
- Part II - Model and TableView
- Part III - Interacting with the User
- Part IV - CSS Styling
- Part V - Storing Data as XML
- Part VI - Statistics Chart
- Part VII - Deployment with e(fx)clipse
Saving User Preferences
Java allows us to save some application state using a class called Preferences. Depending on the operating system, the Preferences are saved in different places (e.g. the registry file in Windows).
We won’t be able to use Preferences to store our entire address book. But it allows us to save some simple application state. One such thing is the path to the last opened file. With this information we could load the last application state whenever the user restarts the application.
The following two methods take care of saving and retrieving Preferences. Add them to the end of your MainApp class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
Persisting Data as XML
At the moment our address application’s data only resides in memory. Every time we close the application, the data is lost. So it’s about time to start thinking persistently storing data.
Why XML?
One of the most common ways to persist data is using a database. Databases usually contain some kind of relational data (like tables) while the data we need to save are objects. This is called the object-relational impedance mismatch. It is quite some work to match objects to relational database tables. There are some of frameworks that help with the matching (e.g. Hibernate, the most popular one) but it still requires quite some work to set up.
For our simple data model it’s much easier to use XML. We’ll use a library called XStream. With just a few lines of code this will allow us to generate XML output like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Reading and Writing Files
Since Java 7 there are some convenient classes to deal with reading and writing files. For a detailed tutorial see Oracle’s File I/O Tutorial.
Since we might need to read/write files in different places of our application we’ll create a handy FileUtil helper class. This class provides one static method for reading from a file and one for writing to a file. Copy the following file into the ch.makery.util package:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | |
Using XStream
To use XStream we need three libraries. Add the following libraries to the project’s lib folder and add them to the build path (right click on libraries).
- xstream-1.4.3.jar - XStream main library
- xmlpull-1.1.3.1.jar - XmlPull to detect available parsers
- xpp3_min-1.1.4c.jar - Xpp3, a fast pull parser
You can also download the three libraries from the XStream download page.
We’ll make our MainApp class responsible for reading and writing the person data. Add the following two methods to the end of MainApp.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
The save method uses xstream.toXML(...) to convert the list of Person objects into an XML representation. The load method uses xstream.fromXML(...) to convert the xml data back to a list of Persons.
If anything goes wrong, an error dialog is presented to the user.
Handling Menu Actions
In our RootLayout.fxml there is already a menu, but we haven’t used it yet. Before we add action to the menu we’ll first create all menu items.
Open the RootLayout.fxml file in Scene Builder and drag the necessary menu items from the library view to the menu bar in the hierarchy view. Create a New, Open…, Save, Save As…, and Exit menu item. You may also use separators between some items.

Hint: Using the Accelerator setting under properties you can set shortcut keys to menu items.
The RootLayoutController
For handling menu actions we’ll need a new controller class. Create a class RootLayoutController inside the controller package ch.makery.address.
Add the following content to the controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
The controller contains an @FXML method for each menu item.
FileChooser
Take note of the methods that use the FileChooser class inside RootLayoutController above. First, a new object of the class FileChooser is created. Then, an extension filter is added so that only files ending in .xml are displayed. Finally, the file chooser is displayed on top of the primary stage.
If the user closes the dialog without choosing a file, null is returned. Otherwise, we get the selected file and we can pass it to the loadPersonDataFromFile(...) or savePersonDataToFile(...) method of MainApp.
Connecting the fxml View to the Controller
Open
RootLayout.fxmlin Scene Builder. Select the rootBorderPane. In the Code view select theRootLayoutControlleras Controller class.Select each menu item in the Hierarchy view. In the Code view under On Action you should see a choice of all the
@FXMLmethods of the controller. Choose the corresponding method for each menu item.
If you don’t see the choices in On Action: Because of a bug in Scene Builder you have to remove the controller from the root, hit enter, and add it again. I had to do this after every restart of Scene Builder! (fixed in Scene Builder 1.1 beta 17 and above!)Close Scene Builder and hit Refresh (F5) on your project’s root folder. This will make Eclipse aware of the changes you made in Scene Builder.
Connecting the MainApp and RootLayoutController
In several places, the RootLayoutController needs a reference back to the MainApp. We haven’t passed the reference to the RootLayoutController yet.
So, open the MainApp class and replace the start(...) method with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Notice the two changes: The lines that give the controller access to the main app and the last three lines to load the last opened person file.
How It Works
Doing a test drive of your application you should be able to use the menus to save the person data to a file and load it again. After a restart, it should automatically load the last used file.
Let’s see how it all works together:
- The application is started using the
main(...)method insideMainApp. - The constructor
public MainApp()is called and adds some sample data. MainAppsstart(...)method is called and initializes the root layout fromRootLayout.fxml. The fxml file has the information about which controller to use and links the view to itsRootLayoutController.- The
MainAppgets theRootLayoutControllerfrom the fxml loader and passes a reference to itself to the controller. With this reference the controller can later access the (public) methods ofMainApp. - At the end of the
start(...)method we try to get the last opened person file fromPreferences. If thePreferencesknow about such an XML file, we’ll load the data from this XML file. This will apparently overwrite the sample data from the constructor.
What’s Next?
In Tutorial Part VI we’ll add a birthday statistics chart.
Download
Source of Tutorial Part V as Eclipse Project.