Kotlin from the Ground Up (Part 2)
Click here to go back to the start of the series where you can view the TOC.
What does this part cover:
- Writing & running a simple program
- Getting to know the language (5 basic commands)
- Scripting with Kotlin
- Getting started with IntelliJ IDEA
- Scratch files in IntelliJ IDEA
- Project structure
- What is Gradle and how does it compare with Maven?
- A few key takeaways
Writing & running a simple program (from CLI)
Reference: https://kotlinlang.org/docs/tutorials/command-line.html
A simple, compiled project:
- Start by creating a directory for your project:
mkdir kt-hello
- Open an editor and create a new .kt file:
vim app.kt
- Enter the following code and save it (
:wq
)fun main(args: Array<String>) {
println("Hello, World!")
} - Compile it with the Kotlin runtime so that anyone can execute it:
kotlinc app.kt -include-runtime -d app.jar
- Now you can run it with Java directly:
java -jar app.jar
You’ll notice the main function has an args
array of type String. This will allow you to read the CLI arguments which we ignore for now but will use later on.
You’ll also notice there is no need to end lines with a semi-colon, although semi-colons must be used to separate commands on the same line, e.g. result.apply { this.add(1); this.add(2) }
What about building a library?
If you are building something in Kotlin that you want to use in another Kotlin project, you can omit the runtime and simply generate the JAR:
kotlinc hello.kt -d hello.jar
If your class has a main
function, you can execute it using the kotlin
command directly:
kotlin -classpath hello.jar HelloKt
5 basic commands to get started with
When I got started with PHP (over 16 years ago) I reached out to a few people on IRC on what was the best way to learn PHP.
Most people had a certain disdain for W3Schools PHP guide and pointed me to the official reference at php.net instead.
One piece of advice I got from someone on ##php on freenode was to learn the 5 most basic but fundamental commands of the language and to mess around with those until I was comfortable, and then move on from there.
In PHP they were include
, echo
, $a = "something";
, $_GET
and $_POST
Well, and the PHP opening/closing tags as well of course: <?php ... ?>
These were enough to open a whole new world to play around with!
Considering PHP was used very much as a templating engine in tandem with HTML, being able to create dynamic sections in between html tags, and include other files on-demand was eye opening (after having only created static websites for years).
So what are 5 basic Kotlin commands to get started with?
val
andvar
let you declare variables, where val is immutable (final) and var is mutable (can be changed), similar toconst/let
in JS. A trick to remember them is to think ofval
as “value” (something you set and forget), andvar
as “variable” (something that changes). Example:val name = "John"
, Kotlin will know this is a string, so you don’t have to explicitly declare the type. Just keep in mind Kotlin is type-safe, so you can’t writename = 1
afterwards.println
outputs text to the screen (Stdout) followed by a newline, it is similar toecho "text\n";
in PHP. The alternative isprint
which does not end in a new line. Example:println("Hi")
. You can also do string interpolation, e.g.val name = "John"; val amt = 2; println("Hi $john, here are your ${1 + amt} books!")
fun
allows you to define a function. There has been a bit of debate on why “fun” was chosen, but you get used to it fast.
Example:fun greetName(name: String) { println("Hi $name") }
package
allows you to define the ‘path’ or domain to which your class/file belongs to. It generally uses Java’s naming convention for packages which is the reverse internet domain name plus the package description. For example, if your company’s name is “laptops xyz”, and your domain is “laptops.xyz”, and you are writing an array helper library, your package name might bepackage xyz.laptops.array_helper
. Click for more info.import
allows you to import classes/functions/properties/names from packages. Example:import java.util.Scanner
. The official docs have a bit more information on packages/imports. This is a simple tutorial including aliasing, e.g.import java.util.Scanner as Scan
An example using all of these:
Create a new file called greeter.kt
package com.example.greeterfun greetName(name: String) {
println("Hi $name!")
}
Notice the type is mentioned after the variable name, this is one major difference with Java where you would type String name
, however it is more similar to Typescript, or PHP method return types. But PHP prefixes the type in the case of function parameters... PHP is weird 😅 don’t ask.
Modify your app.kt
file with the following:
import com.example.greeter.greetNamefun main() {
val name = "John"
greetName(name)
}
Notice we are importing the function directly, you could also import all functions in the file with import com.example.greeter.*
, or a class instead.
If you try to compile it with kotlinc app.kt -include-runtime -d app.jar
you will get an unresolved reference
exception. This is due to the fact that the compiler looks for imports based on packages and not file locations like in Python (or PHP without a bootstrapper).
To allow it to find your references, it you must pass in the directory to the Kotlin compiler, hence why when you start a new project in IntelliJ it will usually include an src
folder inside of which you place your source files.
Try creating an src
folder, place both files in it, and compile the whole of src with the following command: kotlinc src -include-runtime -d app.jar
Now run it: java -jar app.jar
and you’ll get “Hi John!”
Now let’s get the name from the command line arguments!
Modify app.kt with the following:
import com.example.greeter.greetNamefun main(args: Array<String>) {
val name = (if (args.isNotEmpty()) args.first() else "John")
greetName(name)
}
You’ll notice we added args to the constructor of the main function, which is an array of type String, but it can be empty.
In the following line we set name to be the first argument, otherwise it defaults to John; this is how ternary operators work in Kotlin (not to be confused with Lambdas which we’ll cover later on).
Now, an easier approach is to use the Elvis operator which essentially is a null coalesce function: Choose left if not null, else choose right: val name = args.firstOrNull() ?: "John"
If you are feeling brave at this stage, feel free to read this article for more information on null safety in Kotlin… If not, don’t worry, we’ll tackle this topic towards the end of this tutorial 😁
Scripting with Kotlin
Kotlin scripts are files with a .kts
extension. You don’t need to compile them to a JAR, you can run them directly with kotlinc
, for example: kotlinc -script app.kts <args>
Example Kotlin script (src/script.kts):
import java.io.Fileval folders = File(args[0]).listFiles { file -> file.isDirectory() }
folders?.forEach { folder -> println(folder) }
You would run it like this: kotlinc -script src/script.kts ~/
but it might fail in Kotlin 1.3.50 and 1.3.60 due to a bug
You can run it using Java by specifying the location of the Kotlin compiler: java -jar <path_to_compiler> -script src/test.kts ~/
For example:java -jar /usr/local/Cellar/kotlin/1.3.50/libexec/lib/kotlin-compiler.jar -script src/script.kts ~/
As of Kotlin 1.3.70 they have improved support for Kotlin scripts, you can now run them directly with kotlin
without the -script
argument, like this: kotlin myscript.main.kts
. Notice the .main.kts extension, this allows us to avoid the problems with importing other files mentioned below. Read more about kotlin-main-kts here.
Problems with using Kotlin scripts:
Note: This was the case up to Kotlin 1.3.60 and has not been tested in 1.3.70, kotlin-main-kts apparently works around these problems.
Lets say you modified your test script to contain something similar to app.kt:
import com.example.greeter.greetNameval name = args.firstOrNull() ?: "John"
greetName(name)
When you try running it java -jar /usr/local/Cellar/kotlin/1.3.50/libexec/lib/kotlin-compiler.jar -script src/script.kts
it will throw an “unresolved reference” exception, similar to the one we dealt with when trying to compile a single file previously.
In Kotlin (at the time of writing this using 1.3.50) importing in kts
files does not work. You will need to use KScript.
KScript is a wrapper around kotlinc
and according to the docs "kscript provides an easy-to-use, very flexible, and almost zero-overhead solution to write self-contained mini-applications with Kotlin".
You install it with sdk install kscript
and run the script with: kscript src/script.kts
read this tutorial to dive further into kscript)
Change script.kts:
to use the kscript Include annotation:
#!/usr/bin/env kscript
@file:Include("greeter.kt")val lib = Lib()
lib.printString("wow")
Make it executable:chmod +x src/script.kts
and run it: ./src/script.kts
But it still fails in 1.3.50 and 1.3.60 due to a class loader exception.
So it seems we are out of luck with scripts involving more than one Kotlin file up to Kotlin 1.3.60, where the use-case seems to be more for single self-contained commands. However, in Kotlin 1.3.70 and above, kotlin-main-kts seems to work around this issue.
Getting started with IntelliJ IDEA
Follow this short visual guide on how to start a new project in IntelliJ.
You’ll want a few plugins before we start. Open your IntelliJ preferences, go to Plugins → Marketplace, and install Kotlin, Spek, and Spek Framework.
The final two will allow you to easily run JUnit / Spek tests from IntelliJ.
There are a few more things you’ll have to set up in IntelliJ for Gradle builds, but we’ll deal with those in the following part. However, one important thing to check in your preferences is that the correct Java/JDK version is being used (in my case it should be 12 instead of 1.8):
To start a new Kotlin project Go through these steps:
- File → New Project
- Kotlin → JVM
- Select a project name and the correct Java/JDK version you installed previously; the higher the better, with the exception of projects that require a framework that supports only specific versions. If you are in doubt, check out what versions of the JVM are supported by Micronaut/Ktor/Spring Boot/VertX or the framework of your choice.
Auto-completion & Warnings:
If you are not able to run your program from IntelliJ, or you don’t have autocompletion nor warnings/errors (squiggly lines underneath code lines), you will need to configure Kotlin for your project.
Run: Tools → Kotlin → Configure Kotlin in Project
If you still don’t see a green “play” button next to your main function do this:
Why use IntelliJ IDEA?
It makes it easy to dive into functions, run a debugger, run tests (individually and in groups), quickly run Gradle tasks (build + run your application), run VCS tasks (commit, push, etc.), run terminal commands, and create Scratch files which we will cover later. It also supports debugging in Docker.
Additionally, if you paste Java code it will offer to translate it into Kotlin!
What about Android Studio? If you are developing for Android feel free to use Android Studio, which is built upon IntelliJ and has the same functionality plus quite a bit of Android-specific tooling/features.
Gitignore:
IntelliJ (and Gradle/Maven) generate a few build artefacts which you don’t want to check into your VCS. If you use git, you might want to consider creating the following .gitignore
file:
Thumbs.db
.DS_Store
.gradle
build/
target/
out/
.idea
*.iml
*.ipr
*.iws
.project
.settings
.classpath
Scratch files
Scratch files in IntelliJ allow you to quickly test code snippets and visualise the results without having to use Kotlin scripts nor the kotlinc interactive CLI tool.
Create a new Scratch by right clicking on Scratches, or cmd+shift+n:
Code you input will automatically compile and the results will be displayed on the right-hand side:
You can find more information on Kotlin Scratch files, including how to import classes from your project (using the classpath) in this article.
Project structure
When using plain Kotlin without Gradle/Maven/Ant:
The official docs have a simple convention: Use reverse domain naming (like Java) directly under the source root.
If your project is called “calendar”, and you have a “helpers” package which has a few modules such as “StringHelper” and “ArrayHelper”, then your directory structure might look like this:
|- src
|- Application.kt
|- com
|- example
|- helpers
|- StringHelper.kt (pkg: com.example.helpers)
|- ArrayHelper.kt (pkg: com.example.helpers)
The class name of those helpers should be upper camel case (matching the filename).
If those classes are more complex, they could be placed under a subdirectory, and each package should match the path (lowercase or lower camelcase), e.g. com.example.helpers.stringhelper
. You can read more about the package naming conventions here.
Kotlin does not force you to match the path structure, so you could have “helpers” directly under “src” and still use “com.example.helpers” as a package name, but it will issue a warning. Java on the other hand issues an exception.
How does the structure change when using a build tool?
The official docs have conventions for Gradle, Maven and Ant.
Basically they recommend additional directories under src
called main
and test
. Additionally, if you are planning on combining Java and Kotlin, you might want to consider using their recommendation to add kotlin
and java
directories underneath main
. I generally skip this for Kotlin-specific projects.
Wait… you can run Java alongside Kotlin? Yes, we’ll cover that later on ;)
A simple Gradle-based project might look like this:
|- build.gradle.kts
|- src
|- main
| |- kotlin
| | |- Application.kt
| | |- com
| | |- example
| | |- helpers
| | |- StringHelper.kt
| | |- ArrayHelper.kt
| |- resources
| |- logback.xml
| |- application.yml
|- test
|- kotlin
|- ApplicationTest.kt
|- com
|- example
|- helpers
|- StringHelperTest.kt
|- ArrayHelperTest.kt
There is a bit of work to do to set up the directory structure, you must start a Gradle project before creating the directory structure, and load (import) the Gradle project in IntelliJ so that it doesn’t complain when you try to nest directories with packages underneath. We’ll cover this in part 2.
What is Gradle and how does it compare with Maven and Ant?
These three are build & dependency tools, like npm (and grunt + gulp in the old days) are for the JS/Node ecosystem, or composer is for PHP, or pip, Anaconda and Poetry for Python.
They allow you to automate the build process, gathering and compiling dependencies, and allowing extensions and plugins to perform actions on the codebase, like applying linting, running tests, generating code and docs, etc.
This article compares the three options and gives a brief overview of each.
For Kotlin you can choose whichever, but I like the top answer from this question, where it recommends Gradle as a majority of Kotlin projects use it, and Kotlin support in Gradle is more mature.
You can write your Gradle files with either “Groovy” or “Kotlin DSL”, the comments on this question give a bit of insight, but we’ll dive deeper into this in the next part.
The main thing to remember is:
- If you see a build.gradle file it is Gradle DSL (which is a Groovy script)
- If you see a build.gradle.kts file it is Kotlin DSL (.kts = Kotlin script file!)
A few key takeaways
- 5 basic commands:
val/var, println(“”), fun xyz() {}, package, import
- Chain null-safe operators:
val first = name?.firstOrNull()?.toString()
- Be careful with not-null assertion operators
!!
- The classpath tells the JDK which classes to load when running a program
Thanks for reading, click here to go to the next part.