Creating the module

Let's start by creating the new module packt.addressbook.ui. As before, create the module root folder with the same name in the project folder, and then create the module descriptor module-info.java. We already know that we need to depend on packt.contacts and packt.sortutil, so let's add those two dependencies first:

    module packt.addressbook.ui { 
      requires packt.sortutil; 
      requires packt.contact; 
    } 

We need to use the JavaFX libraries in this module, so we need to use the requires clause in the module descriptor to specify this dependency. How do we know what the libraries are? The answer is the same as earlier--using java --list-modules and java -d <module-name>. But before we browse for what modules to depend on, we should know what APIs we need! Let's look at the code we'll need to write to build the UI.

We'll create a Main.java class in the package packt.addressbook.ui. This class will launch the UI. As with any Java FX app, the class that launches the application is required to extend javafx.application.Application. We then override the start method and add the functionality of building the UI in it. This method is called by the JavaFX framework to launch our application. Remember this method! We will revisit this shortly when we execute the code:

    public class Main extends Application { 
 
      public static void main(String[] args) { 
        launch(args); 
      } 
 
      @Override 
      public void start(Stage primaryStage) throws Exception { 
        // Build JavaFX UI and application functionality 
      } 
    } 

In the start method, the logic to get the Contact instances and to sort them is exactly the same as the command-line application in the packt.addressbook module:

    ContactLoader contactLoader = new ContactLoader(); 
    SortUtil sortUtil = new SortUtil(); 
    try { 
          contacts = contactLoader.loadContacts(
"/Users/koushik/code/java9/input.xml"); } catch (ContactLoadException e) { logger.severe(e.getMessage()); System.exit(0); } sortUtil.sortList(contacts);

What's different in this case is what we do with the sorted list of Contacts. We don't just print it to the console. Instead, we will build a JavaFX ListView that displays the list. We'll also add a click handler to each element in the list, so that when a name is clicked, we can display the details of that contact to the right of the list. Here's what we'd like the UI to look like:

Without going into too much detail of how the JavaFX controls are built and displayed, here's the core functionality that builds the list of Contacts from the list that sortutil has sorted, and handles click events on the list item:

    // Create a new JavaFX ListView 
    ListView<String> list = new ListView<String>(); 
    // Collect a String list of Contact names in lastName,
firstName format List<String> listContactNames = contacts.stream()
.map(c -> c.getLastName() + ", " + c.getFirstName()) .collect(Collectors.toList()); // Build an ObservableList from the list of names ObservableList<String> obsContactNames =
FXCollections.observableList(listContactNames); // Pass that to ListView to have them displayed in a list list.setItems(obsContactNames); // Add listener to handle click events list.getSelectionModel()
.selectedItemProperty()
.addListener((obs, oldVal, newVal) -> { // Get the selected index in the ListView int selectedIndex =
list.getSelectionModel().getSelectedIndex(); name.setText(newVal); // Get the Contact instance which was clicked Contact contact = finalContactList.get(selectedIndex); // Set the values to each of the labels on the right street.setText(contact.getAddress().getStreet()); ...

The preceding code wires in the logic we are already familiar with (the ContactLoader and SortUtil) into the JavaFX code that displays that data in a UI for browsing. We have used quite a lot of JavaFX APIs here, as we normally would when building a JavaFX application like this. Now that we know what the APIs we need to use are, we next need to find the modules that exports these APIs and set up dependencies in the packt.addressbook.ui module.

Using the java --list-modules command, we see there are multiple modules associated with JavaFX. Those are the ones that start with the javafx. prefix:

$ java --list-modules 
... 
javafx.base@9
javafx.controls@9
javafx.deploy@9
javafx.fxml@9
javafx.graphics@9
javafx.media@9
javafx.swing@9
javafx.web@9
... 

To know the packages we use, all we need to do is look at the list of imports in the Main.java class. We can examine the package level information for each of the JavaFX modules to get the set of modules that together have all the packages we need.

For example, javafx.base exports javafx.collections that we use in Main.java. So, that's a module we'll be adding. Here are more modules we are interested in. The left column shows the package we need, and used in our Java code. The right column is the Java platform module that exports that package (which we find by running java -d <module-name>):

Package                  Module 
------------------------------------------ javafx.collections javafx.base javafx.scene.control javafx.controls javafx.application javafx.graphics javafx.scene.layout javafx.graphics javafx.geometry javafx.graphics

Based on this, the three modules we'll need to require are javafx.base, javafx.controls, and javafx.graphics. Let's add these three into the packt.addressbook.ui module definition using the requires clause. Here's module-info.java when we are done:

    module packt.addressbook.ui { 
      requires java.logging; 
      requires javafx.base; 
      requires javafx.controls; 
      requires javafx.graphics; 
      requires packt.sortutil; 
      requires packt.contact; 
    } 

While we've found the packages exported by the modules and we are technically not wrong in what we've required, this step could be done in a much better way. We actually needed to require just one JavaFX module here! This is thanks to a certain qualifier called transitive. We will be covering more about what that is and how it affects our dependencies in Chapter 6, Module Resolution, Accessibility, and Readability. But, since we haven't covered it yet, let's go with adding all three JavaFX modules for now.

If you feel like the process of finding these dependencies by running the
--list-modules command is tedious, well, you are not alone! This is something that I hope will quickly be unnecessary once IDEs get support for Java 9. Ideally, an IDE should be able to help us identify the modules based on the packages we import into our Java applications, and preferably add the modules to the module descriptor automatically. This feature might already be available in most of the standard IDEs by the time you are reading this!

OK, so with this, we have all the dependencies established. Let's give this a go! Compile all modules using the  javac command:

$ javac --module-source-path src -d out $(find . -name '*.java')

The code should compile without any errors. Let's try to execute it. Since we are running the Main.java file in the new module packt.addressbook.ui, make sure you specify that in the command this time. Notice that we get an error when we run the code:

$ java --module-path out -m packt.addressbook.ui/packt.addressbook.ui.Main

Exception in Application constructor
Exception in thread "main" java.lang.reflect.InvocationTargetException
...
Caused by: java.lang.IllegalAccessException: class com.sun.javafx.application.LauncherImpl (in module javafx.graphics) cannot access class packt.addressbook.ui.Main (in module packt.addressbook.ui) because module packt.addressbook.ui does not export packt.addressbook.ui to module javafx.graphics
...

The error indicates that the module javafx.graphics is trying to access our Main class and is unable to access it because our module packt.addressbook.ui doesn't export it! You might be wondering what business the module javafx.graphics has with a class we wrote! Why would it need to access our class?

Turns out the answer is because of the way JavaFX works. Remember that I mentioned about the start() method in Main.java and how the JavaFX framework calls that method to launch the application. The framework uses reflection to identify classes that extend the Application class. The framework then uses that information to launch the JavaFX application by calling the start() method.

And there is our problem. In the module descriptor of packt.addressbook.ui, we don't export the package that Main is in, that is packt.addressbook.ui. So, Main is not accessible to any code outside the module, and so JavaFX cannot launch the application! The encapsulation that applied to static access of types outside the module is in effect even for runtime reflective access!

One way to solve this problem is by making Main public. We just need to export the package that the type is in. That's enough for JavaFX to access it. That also actually enables any module to access it! This may or may not be what you want. But for now, let's export the package and make Main.java available externally. We'll revisit this too in Chapter 6, Module Resolution, Accessibility, and Readability, and find a better way to do this.

Here's the final module-info.java:

    module packt.addressbook.ui { 
      exports packt.addressbook.ui;  
      requires java.logging; 
      requires javafx.base; 
      requires javafx.controls; 
      requires javafx.graphics; 
      requires packt.sortutil; 
      requires packt.contact; 
    } 

Compile and run the application again, and everything should work this time around. A GUI window should load with the list of contacts sorted by last name. Clicking on a name should display the details on the right-hand side. You can close the application by clicking the close button on the title bar.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.188.244