read.cash Log in

@marley

Joined 6 January 2020 · 3 posts

Bitcoin Cash FTW

120 KT

0 KT · $18.05 received · 0 KT · $3.47 given

Posts

@marley

Kotlin from the ground up, Part 3 - creating a GUI App In Part 2, you had a taste of how Kotlin works from the interactive command line (REPL). Now let's try our hand at coding a GUI Hello World app. https://read.cash/@marley/kotlin-from-the-ground-up-part-2-the-repl-a2c8f4b7 One of the major reasons for using Java to code GUI apps is so that you can have apps running on Windows, MacOS or Linux using not only a single source code base, but also without even having to recompile your class files/jar! E.g. the same - essentially executable - file can run unmodified on all three OSes! We will be using RxKotlinFX and TornadoFX - which provides Kotlin extensions to the RxJavaFX and JavaFX frameworks, making for very concise, expressive Kotlin code compared to how it would otherwise be expressed in Java. You can, of course, call the respective Java APIs directly from Kotlin if/when necessary. https://github.com/thomasnield/RxKotlinFX http://tornadofx.io/ https://github.com/ReactiveX/RxJavaFX https://openjfx.io/ Download `tornadofx-x.y.z.jar` from https://github.com/edvin/tornadofx/releases/latest and place it in a new, empty directory together with your main source file `Hello.kt` below: import tornadofx.* class MyApp: App(MyView::class) class MyView: View() { override val root = vbox { button("Hello, press me!") } } `MyApp` is derived from `App` and serves as the entry point to the application. `MyApp` is fed with `MyView` which derives from and customizes a `View` by displaying a single button contained within a vbox (vertical box container). The code is the shortest, easiest-to-understand GUI Hello World I've yet to come across and it only gets better as you start to see the benefits of reactive programming. As usual in Java land, writing the code seems far easier than getting things to build and run, and this is what the rest of the post is going to address. https://edvin.gitbooks.io/tornadofx-guide/part1/3_Components.html http://reactivex.io/ Compiling and running for JDK 8 (10?) and below: JavaFX is still part of the JDK in Java 10 and below, so you should be able to get away with including only the TornadoFX framework in the compile-time classpath: > kotlinc -cp tornadofx-x.y.z.jar Hello.kt You can now run the program by calling the main class `MyApp` as shown below: > java -cp "/usr/local/kotlinc/lib/*:*:." MyApp In the run-time classpath `-cp` above, `/kotlinc/lib/*` points to the kotlin runtime libraries, `*` brings in `tornadofx-x.y.z.jar` (and possibly other jars in the current directory) and `.` is so `java` can see the `MyApp.class` in the directory from where it is invoked. **Hint: remember to use** `;` **instead of** `:` **as classpath separator under Windows.** Note: I am using JDK 8 and as the Java Module Platform System (JMPS) was introduced in Java 9, I'm not sure if you *might* also need to adjust the invocation to be more like that shown below for JDK/JFX11 and up. https://en.wikipedia.org/wiki/Java_Platform_Module_System Compiling in JDK 11 and above: You will need to download JavaFX separately. Get a suitable version for your platform from https://gluonhq.com/products/javafx and unpack it. https://gluonhq.com/products/javafx/ As JavaFX is no longer part of the JDK, you have to explicitly include its JAR files when compiling. To save effort in typing, first create a text file that contains the needed classpath as shown below. **Hint: remember to use** `;` **instead of** `:` **as classpath separator under Windows.** -cp "tornadofx-1.7.20.jar:/javafx-sdk-dir/lib/javafx.base.jar:/javafx-sdk-dir/lib/javafx.graphics.jar:/javafx-sdk-dir/lib/javafx.controls.jar" Replace `/javafx-sdk-dir/` with where you unzipped to, then save the snippet above as a text file called, say, `classpath`. Then you can invoke the Kotlin compiler as below: > kotlinc @classpath Hello.kt After the above, you will see a bunch of .class files in the directory corresponding to the two classes in `Hello.kt`. Running in JDK 11 and above: If you are using a version of JavaFX that is modularized (definitely from JDK/JFX 11 and up and possibly in JDK 9 and 10), you have to load JavaFX as modules: > java -cp "/usr/local/kotlinc/lib/*:*:." -p "/javafx-sdk-dir/lib" --add-modules javafx.controls MyApp Rather than pointing to the JavaFX JARs via classpath, you specify paths via `-p` instead and use `--add-modules` to specify individual modules. A module is contained in a respective JAR file, which is usually named the same as the module itself. To test whether a JAR file is modularized or not, you can use `jar -d` or `jar --describe-module` (should be available from Java 9 and up). # old, unmodularized JavaFX runtime JAR file > jar -f /jdk8/jre/lib/jfxrt.jar -d No module descriptor found. Derived automatic module. jfxrt automatic requires java.base mandated contains com.sun.deploy.uitoolkit.impl.fx contains .... contains .... contains .... # modularized JavaFX JAR files > jar -f /javafx-sdk-14.0.1/javafx.base.jar -d javafx.base jar:file:///d:/javafx-sdk-14.0.1/lib/javafx.base.jar/!module-info.class exports javafx.beans exports ... exports ... requires java.base mandated requires ... requires ... qualified exports com.sun.javafx to javafx.controls javafx.fxml javafx.graphics javafx.swing qualified exports ... qualified exports ... As you can see from the above, older, non-module JAR files show a `No module descriptor found` message and are turned into a kind of automatic module by JMPS. [ Aside: Java modules come with additional complications and may seem overengineered, but they have enormous benefits if you're compiling big systems that rely on a lot of packages. For now, just think of modules as JAR files, but with additional info attached to that greatly reduces JAR hell. ] https://www.google.com/search?q=jar+hell Result of running With any luck, you should get a window containing a button saying "Press Me" as shown below for MacOS and Windows (Linux will show something similar as well). In my case, I compiled on Windows and was able to run the class files from both Windows and MacOS to get the above. Linux should work the same too. **Compiling and running from JAR files** As your program becomes more complex and the number of class files grow, you can start compiling them into JAR files (which are just zip files with a bit of additional metadata) before distributing them to end-users. Below is how to do it, the differences again having to do with the module system introduced in Java 9 and the uncoupling of JavaFX from the JDK in later JDKs. in Java 8 (10?) and below > kotlinc -include-runtime -cp tornadofx-x.y.z.jar Hello.kt -d hello.jar > java -cp * MyApp Here you add the `-include-runtime` option so that the kotlin runtime is bundled inside the jar, eliminating the need to put it in the classpath. You can now invoke `java` with `-cp` pointing to the directory where `hello.jar` and `tornadofx-x.y.z.jar` (non-modules) are and invoke the `MyApp` class explicitly. For the traditional way of running a jar file: > java -jar hello.jar no main manifest attribute, in hello.jar you can fix the "no main manifest attribute" error by adding a `Main-Path` and `Class-Path` attribute in the `META-INF/MANIFEST.MF` file inside `hello.jar` as shown below: Manifest-Version: 1.0 Created-By: JetBrains Kotlin Class-Path: tornadofx-x.y.z.jar Main-Class: MyApp As long as `tornadofx-x.y.z.jar` is in the same directory, `java -jar hello.jar` should now properly run the program. in Java 11 and above Because of the situation mentioned here you will need to invoke the jar differently in Java 11 and up. First, compile as below: https://github.com/javafxports/openjdk-jfx/issues/236#issuecomment-426583174 > kotlinc -include-runtime @classpath Hello.kt -d hello.jar > java -p /javafx-sdk-dir/lib --add-modules javafx.controls -cp * MyApp Then use `-p` to indicate directory where JavaFX modules are and load via `--add-modules`. You still use `-cp` to indicate where `hello.jar` and `tornadofx-x.y.z.jar` are and then call `MyApp` directly. For JDK 11, I haven't studied yet how to modify the JAR manifest such that you can invoke it via `jar -jar hello.jar` so that is left as an exercise for the reader. Update: I've discovered that the below works: import com.github.thomasnield.rxkotlinfx.actionEvents import tornadofx.* class MyApp: App(MyView::class) class MyView: View() { override val root = vbox { button("press me").actionEvents() .subscribe { println(it) } } } fun main(args: Array<String>) { launch<MyApp>(args) } or, if you target earlier JVM versions: import javafx.application.Application import com.github.thomasnield.rxkotlinfx.actionEvents import tornadofx.* class MyApp: App(MyView::class) class MyView: View() { override val root = vbox { button("press me").actionEvents() .subscribe { println(it) } } } fun main(args: Array<String>) { Application.launch(MyApp::class.java,*args) } Caveats re JDK versions I am doing this under JDK 8 and JDK 14 so steps for Java versions from 9 to 13 are guesses based on my understanding of the changes that occurred when the modules system was introduced in Java 9 as well as when Java FX was moved out of JDK 11. It took a LOT of tears and sweat figuring out how to build and run successfully from the command line, but I pick up stuff that I won't understand if I immediately use build tools like Gradle, Maven, IDEs. Plus doing it this way is snappier for very small tutorial projects and the complex build tools have their own sets of grief to deal with. If you experience issues or have any suggestions, post in the comments section below to see if I or others can help you out. Apart from the article, comments themselves can also be tipped with Bitcoin Cash (BCH)!

@marley

Kotlin from the ground up, Part 2 - the REPL In part 1 you set up a barebones command-line Kotlin environment with proper `PATH` settings and learned how to compile Kotlin source into a runnable `.class` or `.jar` file. Now, let's explore the interactive REPL. Invoke as below: https://read.cash/@marley/kotlin-from-the-ground-up-part-1-setup-compile-jar-package-ec4512a2 > kotlinc Welcome to Kotlin version 1.3.72 (JRE 1.8.0_181-b13) Type :help for help, :quit for quit >>> and let's try a few cool & useful things such as how to check what a Kotlin object's class is via reflection : https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/ >>> System.getProperties() res0: java.util.Properties! = { .... ..... } /* What type of object is this returning? */ >>> val sp = System.getProperties() /* It's not a string, it's a Properties object */ >>> sp::class.simpleName res1: kotlin.reflect.KClass<out java.util.Properties!> = class java.util.Properties /* Compare to an integer */ >>> val x = 5 >>> x::class res3: kotlin.reflect.KClass<out kotlin.Int> = class kotlin.Int /* You can reflect directly on literals, not just variables */ >>> 42::class res4: kotlin.reflect.KClass<out kotlin.Int> = class kotlin.Int >>> "hello there"::class res5: kotlin.reflect.KClass<out kotlin.String> = class kotlin.String /* What members does the class provide? Let's make a helper function to show them properly */ >>> fun listMembers(x:Any) { println(); for (m in x::class.members) println(m) } >>> listMembers("I'm a string") val kotlin.String.length: kotlin.Int fun kotlin.String.compareTo(kotlin.String): kotlin.Int fun kotlin.String.get(kotlin.Int): kotlin.Char fun kotlin.String.plus(kotlin.Any?): kotlin.String fun kotlin.String.subSequence(kotlin.Int, kotlin.Int): kotlin.CharSequence fun kotlin.String.chars(): java.util.stream.IntStream! fun kotlin.String.codePoints(): java.util.stream.IntStream! fun kotlin.String.equals(kotlin.Any?): kotlin.Boolean fun kotlin.String.hashCode(): kotlin.Int fun kotlin.String.toString(): kotlin.String I might add more REPL stuff to do here, but for now, you can head on to Part 3 where you finally build a GUI app using Kotlin and the TornadoFX framework! https://read.cash/@marley/kotlin-from-the-ground-up-part-3-creating-a-gui-app-4f6872be

@marley

Kotlin from the ground up, Part 1 - setup, compile, jar package Get the command line compiler Visit https://github.com/JetBrains/kotlin/releases/latest, scroll down to the bottom and download `kotlin-compiler-x.y.z.zip` Set up environment First, install the JDK with`JAVA_HOME` pointed to its folder and the `bin` directory under it added to your `PATH` variable. Then unzip `kotlin-compiler-x.y.z.zip` into a directory of your choice (e.g. `d:\kotlinc`or `/usr/local/kotlinc`) and add `d:\kotlinc\bin` or `/usr/local/kotlinc/bin` to your path. Write a test program, save it fun isEven(k:Int): Boolean =  k % 2 == 0 fun main() {   println( "${ listOf(1,2,8,8,10,3).filter(::isEven) }" ) } and compile it > kotlinc iseven.kt This generates `IsevenKt.class` and you can invoke it like below so needed classes in Kotlin's standard library can be found: > java -cp "d:\kotlinc\lib\kotlin-stdlib.jar;." IsevenKt [2, 8, 8, 10] e.g. the classpath (`-cp`) should include the Kotlin Standard Library jar file and any classes in the current directory. Alternatively, by using `-include-runtime`, you can create a standalone single-file jar that you can run from any environment with a java runtime installed: >kotlinc -include-runtime iseven.kt -d iseven.jar >java -jar iseven.jar [2, 8, 8, 10] Postscript: You will eventually find yourself using an IDE like IntelliJ and build system beasts like Gradle/Maven/etc. but doing it manually like above & figuring out how to fix the errors and warnings that crop up is very educational and will deepen your knowledge wrt troubleshooting, library/package structure, etc. You are welcome to post such issues in the comments below and see if I and others can help you troubleshoot. As we are on read.cash, it's also an opportunity to tip and be tipped Bitcoin Cash (BCH) not just on the article but on comments as well! It is very helpful to know which packages are imported by default as mentioned in https://kotlinlang.org/docs/reference/packages.html: https://read.cash Now let's fire up the Kotlin REPL and do some cool stuff in Part 2! https://read.cash/@marley/kotlin-from-the-ground-up-part-2-the-repl-a2c8f4b7