Hello world with Kotlin and JavaFX

Introduction

Java Book Club BOF has been holding a "Kotlin in Action" book club since January 2018.

If you want to learn Kotlin with all your might, I decided to rewrite the GUI in which the Hello world message flows in the JavaFX I created earlier in Kotlin because I wanted to make something.

This article aims to replace the above Java code with Kotlin. Therefore, please refer to the above blog for the explanation of JavaFX.

Store the created code in the following repository (URL of repository viewer)

http://www.torutk.com/projects/swe/repository/revisions/master/show/learn/kotlin/javafx/MessageBoard

Preparing the environment

If you want to avoid the trouble of building an environment and utilize a lot of coding support such as code completion, you can almost choose IntelliJ IDEA, the development environment of JetBrains, which develops Kotlin, or Android Studio, which is based on IntelliJ IDEA. is.

In the article "Hello world flowing with JavaFX", I introduced how to create it in the Java command line environment. Knowledge of the Java runtime environment is useful and should be known. However, in the case of Kotlin, there is a lot of work in preparing the Kotlin environment in addition to the Java environment, and I do not have much opportunity to utilize that knowledge, so this time I will use the IDE from the beginning.

IntelliJ IDEA Community Edition IntelliJ IDEA is a commercial development environment product, but Community Edition is provided free of charge. Kotlin's development environment is included in Community Edition, so I will use it this time.

I think that it can be used in Android Studio (also provided free of charge by Google) in almost the same way, but I have not verified it.

Japanese localization of IntelliJ IDEA (if you like)

Pleiades, a familiar Japanese localization tool in Eclipse, also supports Japanese localization of IntelliJ IDEA. Next, I'm writing a little introduction method.

IntelliJ IDEA fonts in Japanese Windows environment (if you like)

In the Japanese Windows environment, European fonts are beautiful, but Japanese fonts are MS Gothic and jagged. Next, I'm writing a setting to clean the font a little.

Create a Kotlin project

In IntelliJ IDEA, create a project for a flowing Hello world program.

Create Kotlin source file

MessageBoard.kt


class MessageBoard {
}

From here, we will call JavaFX and create a flowing Hello world.

Hello world programming with Kotlin and JavaFX

Call JavaFX in Kotlin and program the GUI for the flowing Hello world message.

Inherit JavaFX Application class

JavaFX inherits the Application class and prepares a class that will be the entry point for JavaFX processing.

If you proceed with the knowledge of a Java programmer who has bitten Kotlin ...

Let the class created by the application inherit the JavaFX Application class.

MessageBoard.kt


class MessageBoard : Application {
}

In Kotlin, inheritance is specified by a colon symbol. Here, the Application class needs to be imported, so in the IntelliJ IDEA code you will see a pop-up message like this:

image.png

Follow the pop-up message and press Alt + Enter to select javafx.application.Application from the candidate list.

image.png

An import statement is generated.

MessageBoard.kt


import javafx.application.Application

class MessageBoard : Application {
}

However, it is still incomplete and is in a compile error state. The following screen shows the situation where the error content is popped up.

image.png

You need to define a start method. From the Code menu> Implement Method, generate the abstract method start of the Application class.

image.png

The generated code is:

MessageBoard.kt


import javafx.application.Application
import javafx.stage.Stage

class MessageBoard : Application {
    override fun start(primaryStage: Stage?) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

However, there are still errors. A red wavy line is drawn below the Application. When you move the cursor to Application, the error content will pop up as follows.

image.png

The message is "This type has a constructor, and thus must be initialized here". What is this? Details will be described in the next section.

When inheriting in a class that does not have a constructor

“Kotlin in Action” on page 106 of Section 4.2.1

open class Button ← Generates default constructor with no arguments

If you inherit from the Button class and don't have a constructor, you need to explicitly call the superclass's constructor, even if the superclass's constructor has no arguments.

class RadioButton : Button()

So, since we don't define a constructor in the MessageBoard class this time, the correct answer is to put parentheses in the inherited superclass.

MessageBoard.kt


import javafx.application.Application
import javafx.stage.Stage

class MessageBoard : Application() {
    override fun start(primaryStage: Stage?) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

Now you have the code with the compilation error removed.

Definition location of main method

But I still can't do it. Define the main method that is the entry point of the program. In Kotlin, instead of defining the main method as a static method in a class like Java, it is defined as a top-level function.

MessageBoard.kt


class MessageBoard : Application() {...}

fun main(args: Array<String>) {
    //Write the code that calls the launch method of the Application class here
}

Kotlin does not have static methods / static fields, but it is possible to access static methods / static fields defined in Java classes, and their calling expressions are the same as in Java.

The JavaFX Application class launch method has different arguments when called from within a subclass of the Application class and when called from outside that subclass. This time, since it is from outside the subclass, Java calls the launch method of the following format.

public static void launch(
        Class<? extends Application> appClass,
        String... args
)

There are two challenges here:

The first is to fetch the class reference (KClass type) with :: class for the class, and then call and get the extended property java of KClass type.

The second uses the spread operator to expand the array.

fun main(args: Array<String>) {
    Application.launch(MessageBoard::class.java, *args)
}

Where to specify variadic arguments with the spread operator, see "Kotlin in Action", Section 3.4.2 (p.76).

Kotlin needs to explicitly retrieve the contents of the array, so that all the elements of the array are independent arguments to the called function. Technically, this feature is called using the spread operator, but it's actually as simple as putting a * in front of the corresponding argument.

fun main(args: Array<String>) {
    val list = listOf("args: ", *args)← Spread operator retrieves the contents of an array
    print(list)
}

There is.

This completes the minimal implementation. Let's run it (although the window isn't displayed yet).

Settings for execution

If you define the main function at the top level of the file, the following icon will be added to the left side of the line.

image.png

Click this icon to pop up the Run / Debug / Run Coverage options. Select Run.

image.png

The code will then be built and executed. When you execute from this icon for the first time, [Execute] is enabled from the [Execute] menu, and the class name (file name) of the file in which the main function is defined in the list in [Execute] menu> [Execute startup configuration]. A class with the name Kt added to) will be added and can be selected.

image.png

When executed, the template implementation code TODO of the start method is called to display an exception.

"C:\Program Files\Java\jdk-9\bin\java" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\lib\idea_rt.jar=52509:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\work\java\repos\swe.primus\learn\kotlin\javafx\MessageBoard\out\production\MessageBoard;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\plugins\Kotlin\kotlinc\lib\kotlin-stdlib.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\plugins\Kotlin\kotlinc\lib\kotlin-reflect.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\plugins\Kotlin\kotlinc\lib\kotlin-test.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\plugins\Kotlin\kotlinc\lib\kotlin-stdlib-jdk7.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.3.1\plugins\Kotlin\kotlinc\lib\kotlin-stdlib-jdk8.jar" MessageBoardKt
Exception in Application start method
Exception in thread "main" Exception in thread "Thread-2" java.lang.RuntimeException: Exception in Application start method
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:973)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:198)
	at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: kotlin.NotImplementedError: An operation is not implemented: not implemented
	at MessageBoard.start(MessageBoard.kt:6)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:919)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$11(PlatformImpl.java:449)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417)
	at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
	at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:175)
	... 1 more
java.lang.IncompatibleClassChangeError

View top-level window

It's finally time to implement. First, describe the visualization of stage with the start method. As a Java programmer, I would code the following, which is a compilation error. The point is that the argument type is Stage? And has a? At the end.

MessageBoard.kt


    override fun start(primaryStage: Stage?) {
        primaryStage.show()
    }

The compilation error is displayed in the following pop-up.

image.png

The argument types of the start method are Stage? Type and nullable type. Therefore, it is necessary to check for null before calling the method or to call the method for null.

Here, if the argument primaryStage is null, nothing is done, so the show method is called with the safe call operator (?.).

MessageBoard.kt


    override fun start(primaryStage: Stage?) {
        primaryStage?.show()
    }

In "Kotlin in Action" Section 6.1.3 (p.181)

The safe-call operator ?. It combines null checking and method invocation into a single operator. (Omitted) That is, if the value you are trying to call a method is not null, the method call will be executed normally, if the value is null, the call will be skipped and null will be used as the value instead.

There is.

When run, an empty window will appear.

image.png

Message placement

Display the text in an empty window.

First, generate a Text that displays the characters. The difference from Java is that there is no new operator, and the variable type is declared by immutable val.

MessageBoard.kt


val message = Text("Hello, world. This is JavaFX from Kotlin.")

Create a group to group JavaFX nodes together. Instance creation and variable types are similar to Text above. Next, what was called setLayoutY (50d) in Java becomes an assignment statement to a property in Kotlin. Also, in java, Kotlin does not have a D (or d) that indicates a Double numeric literal, so the decimal point is specified as .0. By the way, if you write 50 and an integer (Int type) literal, an error will occur if the types are different.

MessageBoard.kt


val group = Group(message)
group.layoutY = 50.0

Generate a Scene. As for the size (width, height), if the type is different for integers, an error will occur, so the decimal point is specified. Also, in java, it is set to primaryStage with the setScene method, and in Kotlin, it is assigned to a property.

MessageBoard.kt


        val scene = Scene(group, 320.0, 160.0)
        primaryStage?.scene = scene

The entire start method looks like this:

MessageBoard.kt


    override fun start(primaryStage: Stage?) {
        val message = Text("Hello, world. This is JavaFX from Kotlin.")
        val group = Group(message)
        group.layoutY = 50.0
        val scene = Scene(group, 320.0, 160.0)
        primaryStage?.scene = scene
        primaryStage?.show()
    }

I will do it.

image.png

Send a message

Use JavaFX animations to move the text from right to left.

In the start method, define TranslateTransition for animation.

MessageBoard.kt


val messageTransition = TranslateTransition(Duration.seconds(8.0), message)
messageTransition.fromX = 320.0
messageTransition.toX = -320.0
messageTransition.interpolator = Interpolator.LINEAR
messageTransition.cycleCount = TranslateTransition.INDEFINITE

Again, the difference is that you don't need the new operator and the set method is an assignment to a property, but it's almost the same as Java code. Also, call play () at the end.

MessageBoard.kt


messageTransition.play()

When executed, the flowing Hello world will be displayed. The following is an animated GIF of the displayed screen at 10fps (the crazy thing is the problem of FPS when making an animated GIF, JavaFX draws at a maximum of 60fps).

HelloWorld.gif

In the above code, the description of TranslateTransition is redundant for Kotlin (property assignment to local variable appears repeatedly), so apply is used to reduce the amount of description. The entire start method after reduction is shown below.

MessageBoard.kt


    override fun start(primaryStage: Stage?) {
        val message = Text("Hello, world. This is JavaFX from Kotlin.")

        val group = Group(message)
        group.layoutY = 80.0

        val scene = Scene(group, 320.0, 160.0)

        primaryStage?.scene = scene
        primaryStage?.show()

        TranslateTransition(Duration.seconds(8.0), message).apply {
            fromX = 320.0
            toX = -320.0
            interpolator = Interpolator.LINEAR
            cycleCount = TranslateTransition.INDEFINITE
        }.play()

    }

Appearance adjustments, colors and fonts

Finally, set the color and size of the message text.

MessageBoard.kt


    override fun start(primaryStage: Stage?) {
        val message = Text().apply {
            text = "Hello, world. This is JavaFX from Kotlin."
            fill = Color.DARKMAGENTA
            font = Font.font("Serif", FontWeight.SEMI_BOLD, 32.0)
        }
        val messageWidth = message.layoutBounds.width
        val messageHeight = message.layoutBounds.height

        val group = Group(message)
        group.layoutY = messageHeight * 2

        val scene = Scene(group, messageWidth, messageHeight * 3)

        primaryStage?.scene = scene
        primaryStage?.show()

        TranslateTransition(Duration.seconds(8.0), message).apply {
            fromX = messageWidth
            toX = -messageWidth
            interpolator = Interpolator.LINEAR
            cycleCount = TranslateTransition.INDEFINITE
        }.play()
    }

I will do it.

HelloWorldColorFont.gif

Distribution of flowing Hello world

This time, the goal is to put it together in a viable JAR.

Generate a executable JAR in IntelliJ IDEA

In the NetBeans IDE, when I created a Java project, it created a JAR file that could be executed without any additional settings.

After investigating that IntelliJ IDEA should be able to do it, you can generate an executable JAR file by following the steps below.

image.png

Which is the main class in Kotlin?

You must specify the main class. In Kotlin, the main function is written at the top level of the file. When compiled into Java bytecode, it will be a class named after the file name (included in the class MessageBoardKt if the main function is defined at the top level of the MessageBoard.kt file).

When building and executing with IntelliJ IDEA, the following class files will be generated under out \ production \ MessageBoard under the project directory.

Examine the methods defined in each class file with the JDK's javap command.

D:~> javap MessageBoard
Compiled from "MessageBoard.kt"
public final class MessageBoard extends javafx.application.Application {
  public void start(javafx.stage.Stage);
  public MessageBoard();
}

D:~>javap MessageBoardKt
Compiled from "MessageBoard.kt"
public final class MessageBoardKt {
  public static final void main(java.lang.String[]);
}

The class that has the main method is MessageBoardKt.class. So, in the continuation of the previous one, specify the main class on the next screen. Click […] at the right end of the main class column.

image.png

This will bring up the Select Main Class screen. On the Search by Name tab, enter part of the main class name (here "M") as it is initially blank. Then, the class corresponding to that name will be listed, so select [MessageBoardKt] here.

image.png

The JAR file field from the library is set to Extract to target JRA by default. This will include the library's class files in the executable JAR file when using a non-Java SE standard library. [Copy to output directory and link via manifest] is the difference between preparing a library file separately from the executable JAR file and writing a reference to the library file in the manifest in the executable JAR file.

This time, we will leave the default so that it can be executed by distributing only one JAR file. Click OK to display the configuration as shown below.

image.png

If you check [Include in project build], a JAR file will be generated when the build is executed. If you don't like the time it takes, leave it unchecked and choose Build menu> Build Artifacts to generate the JAR file. Select Build / Rebuild with the name of the configuration you just created.

image.png

The JAR file was generated in the project directory out \ artifacts \ MessageBoard_jar \ MessageBoard.jar. The capacity is as large as 3.3MB. This is because it also includes the Kotlin execution library.

If you distribute this JAR file independently, you can execute the program created by Kotlin in the environment where JRE is included.

Advanced version

Apply decomposition declaration to Java class

If the following code starts to feel verbose,

val messageWidth = message.layoutBounds.width
val messageHeight = message.layoutBounds.height

I want to apply the decomposition declaration

val (messageWidth, messageHeight) = message.layoutBounds

I want to write. However, the Java class does not define the methods component1 (), component2 (), ... for applying the decomposition declaration.

Therefore, define it with an extension method.

operator fun Bounds.component1(): Double = width

operator fun Bounds.component2(): Double = height

This will allow the Bounds class to be used in decomposition declarations.

However, this is a big overkill. It is "no grace" (it is unavoidable to grace it).

Recommended Posts

Hello world with Kotlin and JavaFX
Hello world with Kotlin and Tornado FX
Hello World with Docker and C
Hello World with GWT 2.8.2 and Maven
"Hello, World!" With Kotlin + CLI in 5 minutes
Hello World with Micronaut
Compare Hello, world! In Spring Boot with Java, Kotlin and Groovy
Hello World with Spring Boot!
Hello World with VS Code!
Hello World with Spring Boot
Hello World with SpringBoot / Gradle
Hello, World! With Asakusa Framework!
Until "Hello World" with Spring Boot
(Intellij) Hello World with Spring Boot
Hello World with GlassFish 5.1 + Servlet + JSP
Create PDF with itext7 ~ Hello World ~
Drag and drop files with JavaFX
"Hello world" for ImageJ with Eclipse
Android OS (7.1.2) build and Hello World
Hello world in Java and Gradle
[Swift] Create a project with Xcode (ver 12.1) and display "Hello, World!"
Until you run Hello World of JavaFX with VS Code + Gradle
Hello World with Eclipse + Spring Boot + Maven
Hello world with Java template engine Thymeleaf
Scaling and translation with JavaFX Canvas (Revenge)
Java development with Codenvy: Hello World! Run
How Spring Security works with Hello World
(IntelliJ + gradle) Hello World with Spring Boot
Minimal Java environment construction and Hello World
JavaFX and HiDPI
HelloFX with JavaFX
Read "Hello world"
Try writing "Hello, World" with a combination of various languages and libraries
Java, Hello, world!
Java Hello World
Hello world! With Spring Boot (Marven + text editor)
Hello World at explosive speed with Spring Initializr! !! !!
Build Java environment and output hello world [Beginner]
Run JSP Hello World with Tomcat on Docker
Until you install Gradle and output "Hello World"
[Java] Hello World with Java 14 x Spring Boot 2.3 x JUnit 5 ~
Show a simple Hello World with SpringBoot + IntelliJ
Try to display hello world with spring + gradle
You can eliminate @Param with Kotlin 1.1 and MyBatis 3.4.1+! !!
A simple rock-paper-scissors game with JavaFX and SceneBuilder
First JavaFX ~ Easy introduction Hello world GUI creation ~
Easy to display hello world with Rails + Docker
Hello World with Java Servlet and JSP (Easy web server startup with Maven + Jetty)
Merry Christmas with JavaFX !!
"Hello World" in Java
Hello World (REST API) with Apache Camel + Spring Boot 2
JavaFX buttons and labels
Java Learning (1)-Hello World
Read System.out.println ("hello, world")
Let's write Hello World
Hello world in node.js
Introduce JavaFX 15 and do GUI development with VS Code
Hello World in Java
Studying Java-Part 1-Hello World
I want to transition screens with kotlin and java!
Prepare the environment for java11 and javaFx with Ubuntu 18.4