Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Distributing JavaFX Applications that use automodules

I have created a JavaFX application. It runs perfectly in my Intellij IDE. Now I want to distribute the application - i.e. I want to obtain an installer that users could download and then it would install the application for them.

I found a very interesting article about this here. This blog article basically describes what I want to achieve. There are two differences though:

  1. I am using Maven and not Gradle

  2. I have dependencies which use automodules such as iText7 and apache.commons.lang3

The usage of automodules is making things very complicated. There is a GitHub project called ModiTect (here) that has been written to solve these issues. I have no experience in using ModiTect though and even my Maven knowledge is barely existent (meaning: I don't really know what I am doing in the pom.xml).

What I am looking for is an explanation (step-by-step) as on how to integrate ModiTect (and if necessary jpackage) into my pom.xml in order to obtain an installer for my JavaFX application that uses automodules (and also a sqlite database, which shouldn't be a problem though).

Can somebody provide this explanation or refer me to a tutorial?

I provide a MWE at the end of this question. The MWE ist a TestApp. To illustrate the problem, run the application and press the "Print PDF" button. A pdf is created in resources --> pdf

The MWE will compile and run when executing javafx:run There will be an error related to the usage of automodules when executing javafx:jlink

I don't know how to fix this. ModiTect appears to be a promising addon. Another possible way can be found in this GitHub repo. But as I said before: My Maven knowledge is not sufficient to really grasp what is going on here. Any help would mean a lot to me!

MWE:

Project Structure

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.company</groupId>
    <artifactId>TestApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>15.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>15.0.1</version>
        </dependency>

        <dependency>
            <groupId>de.jensd</groupId>
            <artifactId>fontawesomefx-fontawesome</artifactId>
            <version>4.7.0-9.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.34.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>kernel</artifactId>
            <version>7.1.14</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>layout</artifactId>
            <version>7.1.14</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>io</artifactId>
            <version>7.1.14</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.7.30</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>15</release>
                    <source>15</source>
                    <target>15</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.5</version>
                <configuration>
                    <mainClass>com.company.TestApp</mainClass>
                </configuration>
            </plugin>

        </plugins>
    </build>
</project>

module-info.java:

module com.company {
    requires javafx.controls;
    requires javafx.fxml;
    requires java.sql;
    requires org.apache.commons.lang3;
    requires kernel;
    requires layout;
    requires io;
    requires sqlite.jdbc;
    requires javafx.graphics;

    opens com.company to javafx.fxml;
    opens com.company.controllers to javafx.fxml;

    exports com.company;
    exports com.company.controllers;
}

TestAppController.java:

package com.company.controllers;


import javafx.event.ActionEvent;

import javafx.fxml.FXML;

import javafx.scene.control.Button;
import javafx.scene.control.TextArea;

import org.apache.commons.lang3.StringUtils;

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;

import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.Document;

import java.io.FileNotFoundException;


public class TestAppController {

    @FXML
    private TextArea taText;

    @FXML
    private Button btnPrint;


    public void handleButtonAction(ActionEvent event) {
        if (event.getSource() == btnPrint) {
            setTaText();
            printPdf();
        }
    }

    public void setTaText() {
        taText.setText(StringUtils.leftPad("Random Text left padded by 50", 50));
    }

    public void printPdf() {
        String directoryString = "src/main/resources/com/company/pdf";

        try {
            String filepath = directoryString + "/" + "pdf_1" + ".pdf";
            PdfWriter writer = new PdfWriter(filepath);
            PdfDocument pdf = new PdfDocument(writer);
            Document document = new Document(pdf);
            document.add(new Paragraph(taText.getText()));
            document.close();
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
            return;
        }

    }
}

TestApp.java:

package com.company;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class TestApp extends Application {

    public static void main(String[] args) {
        launch(args);
    }


    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("testApp.fxml"));
        Scene scene = new Scene(root);

        primaryStage.setScene(scene);
        primaryStage.setTitle("Test");

        primaryStage.show();
    }
}

testApp.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.text.Font?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.company.controllers.TestAppController">
   <top>
      <AnchorPane prefHeight="60.0" prefWidth="600.0" style="-fx-background-color: #337DFF;" BorderPane.alignment="CENTER" />
   </top>
   <center>
      <AnchorPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button fx:id="btnPrint" layoutX="240.0" layoutY="155.0" mnemonicParsing="false" onAction="#handleButtonAction" prefHeight="25.0" prefWidth="120.0" style="-fx-background-color: #337DFF;" text="Print PDF" textFill="WHITE">
               <font>
                  <Font name="System Bold" size="15.0" />
               </font>
            </Button>
            <TextArea fx:id="taText" layoutX="125.0" layoutY="44.0" prefHeight="82.0" prefWidth="350.0" />
         </children>
      </AnchorPane>
   </center>
</BorderPane>
like image 546
Luk Avatar asked Apr 10 '21 14:04

Luk


People also ask

How do I package a JavaFX application?

The recommended way to package JavaFX applications is to use a collection of Ant tasks (ant-javafx. jar), provided with the JavaFX SDK and also with JDK 7 Update 6 or later. NetBeans IDE uses these Ant tasks to package JavaFX projects. Embedded packaging support in NetBeans IDE covers most of the typical use cases.

Which JavaFX application's lifecycle method is used to start an application and make it visible to user o A init ()?

Init() method is called. The start() method is called. The launcher waits for the application to finish and calls the stop() method.


1 Answers

Instead of the javafx maven plugin you could use the moditect plugin to create missing module-info to auto module dependencies and then build the image with moditect.

Such a pom for you could be something like:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.company</groupId>
<artifactId>TestApp</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
    <appName>TestApp</appName>
    <launcherName>testapp</launcherName>
    <moduleName>com.company</moduleName>
    <mainClass>com.company.LoginApp</mainClass>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>15</maven.compiler.source>
    <maven.compiler.target>15</maven.compiler.target>
    <version.java>15</version.java>
</properties>

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>15.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>15.0.1</version>
    </dependency>

    <dependency>
        <groupId>de.jensd</groupId>
        <artifactId>fontawesomefx-fontawesome</artifactId>
        <version>4.7.0-9.1.2</version>
    </dependency>

    <dependency>
        <groupId>org.xerial</groupId>
        <artifactId>sqlite-jdbc</artifactId>
        <version>3.34.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>

    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>kernel</artifactId>
        <version>7.1.14</version>
    </dependency>

    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>layout</artifactId>
        <version>7.1.14</version>
    </dependency>

    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>io</artifactId>
        <version>7.1.14</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <release>${version.java}</release>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>3.1.2</version>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/modules</outputDirectory>
                        <overWriteReleases>false</overWriteReleases>
                        <overWriteSnapshots>false</overWriteSnapshots>
                        <overWriteIfNewer>true</overWriteIfNewer>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.moditect</groupId>
            <artifactId>moditect-maven-plugin</artifactId>
            <version>1.0.0.RC1</version>
            <executions>
                <execution>
                    <id>add-module-info-to-dependencies</id>
                    <phase>package</phase>
                    <configuration>
                        <outputDirectory>${project.build.directory}/modules</outputDirectory>
                        <overwriteExistingFiles>true</overwriteExistingFiles>
                        <modules>
                            <module>
                                <artifact>
                                    <groupId>com.itextpdf</groupId>
                                    <artifactId>kernel</artifactId>
                                </artifact>
                                <moduleInfo>
                                    <name>kernel</name>
                                </moduleInfo>
                            </module>
                            <module>
                                <artifact>
                                    <groupId>com.itextpdf</groupId>
                                    <artifactId>layout</artifactId>
                                </artifact>
                                <moduleInfo>
                                    <name>layout</name>
                                </moduleInfo>
                            </module>
                            <module>
                                <artifact>
                                    <groupId>com.itextpdf</groupId>
                                    <artifactId>io</artifactId>
                                </artifact>
                                <moduleInfo>
                                    <name>io</name>
                                </moduleInfo>
                            </module>
                            <module>
                                <artifact>
                                    <groupId>org.xerial</groupId>
                                    <artifactId>sqlite-jdbc</artifactId>
                                </artifact>
                                <moduleInfo>
                                    <name>sqlite.jdbc</name>
                                </moduleInfo>
                            </module>
                            <module>
                                <artifact>
                                    <groupId>org.apache.commons</groupId>
                                    <artifactId>commons-lang3</artifactId>
                                </artifact>
                                <moduleInfo>
                                    <name>org.apache.commons.lang3</name>
                                </moduleInfo>
                            </module>
                            <module>
                                <artifact>
                                    <groupId>org.slf4j</groupId>
                                    <artifactId>slf4j-api</artifactId>
                                </artifact>
                                <moduleInfo>
                                    <name>org.slf4j</name>
                                </moduleInfo>
                            </module>
                        </modules>
                        <module>
                            <mainClass>${mainClass}</mainClass>
                            <moduleInfoFile>${project.build.sourceDirectory}/module-info.java</moduleInfoFile>
                        </module>
                        <jdepsExtraArgs>
                            <args>--multi-release</args> <args>15</args>
                            <args>--ignore-missing-deps</args>
                        </jdepsExtraArgs>
                    </configuration>
                    <goals>
                        <goal>add-module-info</goal>
                    </goals>
                </execution>
                <execution>
                    <id>create-runtime-image</id>
                    <phase>package</phase>
                    <goals>
                        <goal>create-runtime-image</goal>
                    </goals>
                    <configuration>
                        <modulePath>
                            <path>${project.build.directory}/modules</path>
                        </modulePath>
                        <modules>
                            <module>${moduleName}</module>
                        </modules>
                        <launcher>
                            <name>${launcherName}</name>
                            <module>${moduleName}</module>
                        </launcher>
                        <compression>2</compression>
                        <stripDebug>true</stripDebug>
                        <outputDirectory>${project.build.directory}/jlink-image</outputDirectory>
                        <ignoreSigningInformation>true</ignoreSigningInformation>
                    </configuration>
                </execution>

            </executions>
        </plugin>
        <plugin>
            <groupId>com.github.akman</groupId>
            <artifactId>jpackage-maven-plugin</artifactId>
            <version>0.1.2</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>jpackage</goal>
                    </goals>
                    <configuration>
                        <name>${appName}</name>
                        <type>IMAGE</type>
                        <runtimeimage>${project.build.directory}/jlink-image</runtimeimage>
                        <module>${moduleName}/${mainClass}</module>
                    </configuration>
                </execution>
            </executions>
        </plugin>

    </plugins>
</build>

</project>

My first tests seems to be successful but a few items might need more work (e.g. I do not like to have the --ignore-missing-deps argument)

Maybe this helps a little to get you forward.

like image 105
Konrad Neitzel Avatar answered Nov 12 '22 23:11

Konrad Neitzel