ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Kotlin Native: watch your files

“white and blue dream catcher” by Dyaa Eldin on Unsplash

When you write a command-line utility, last thing you want is to be dependent on the fact that somebody has installed JVM/Node/Ruby for you.

What you do want, though, is to have a single executable binary. And not to fuss with memory management, of course.

For that reason, whenever I needed a simple command-line utility in the recent years, I would write it in Go.

Go syntax is quite simple, produces a single executable binary with runtime built in, and it has GC, which is nice.

Kotlin Native promises something very similar, though. Familiar syntax, single binary, GC. Let’s see how it stands to its promises.

Task that lies before us: creating a simple file watcher.

It should receive as an argument which file to watch, and how often should it check for any changes.

If the file has changed, it would copy it aside, giving it a new name.

Putting it down as an algorithm in no particular language, it should be something like this:

fileToWatch = getFileToWatch()
howOftenToCheck = getHowOftenToCheck()
while (!stopped) {
if (hasChanged(fileToWatch)) {
copyAside(fileToWatch)
}
sleep(howOftenToCheck)
}

After we got some basic idea of what we want to achieve, let’s start putting it down.

First thing we’ll need is an IDE. If you’re anything like me and hadn’t written C code in your life (OK, I had, in 2004), you’re in for a surprise. You cannot write Kotlin Native code in IntelliJ Idea. So, you’ll have to get something like CLion first.

Then comes the toolchain. If you’re on OSX, CLion will most likely detect your toolchain automatically. But if you’re on Windows, and hadn’t done any C development before, you’ll have to follow JetBrains tutorial on configuring CLion.

Say you’ve done all that. Are we ready yet?

Almost. Due to its still somewhat experimental nature, Kotlin Native plugin doesn’t come bundled with CLion by default. So before you see this “Kotlin/Native Application” option in your “New Project” dialog, you’ll need to download and install the plugin.

So, we have a brand new Kotlin Native project, which is based on Gradle, and even more interesting, on Kotlin Script flavor of it.

First, we’ll take a look at build.gradle.kts

plugins {
id("org.jetbrains.kotlin.konan") version("0.8.2")
}

konanArtifacts {
program("file_watcher")
}

The only plugin we use is Konan, which allows us to do all this native magic.

In konanArtifacts we specify what’s the name of our executable. So after successful compilation we should get file_watcher.kexe

Now we begin writing our code. It all starts very simple:

Usually your command line utility will have optional arguments, but for the simplicity we force the user to always provide us both the path and the interval arguments.

Abstracting the path into a class called File instead of using java.io.File may also seem strange to Kotlin developers, but I promise you’ll see the reason behind it soon.

If you’re not familiar with require(), it’s just a nice way to validate arguments. We could have written something like that instead:

Now the fun part starts.

We’ll play a game. We’re going to write Kotlin code. But each time you would like to use a Java class, you must say “Oops”. Ready?

We want our loop to print a dot and sleep for interval period in seconds.

So, in Kotlin Native you can’t use Java classes.

Kotlin classes are fine. Java are not supported. That’s also the reason we didn’t use java.io.File and invented our own File class.

So, what can we use instead of Java classes and methods? C functions!

Once we’re past our first bump into C land, let’s see (no pun intended) what our File looks like. We’ll start with exists() method:

So, our File is a data class, which gives us a nice toString() implementation out of the box.

Our exists() method calls access() C function under the hood. In case such path doesn’t exist, function will return -1

So far so good? Let’s look at the modified() method then:

Notice that in the previous example, I didn’t use single-expression function, and here I don’t use type inference (: Long)

That’s on purpose. Writing Kotlin Native code may get confusing as it is. Focus first on your code being readable.

Now, there are two points of interest there.

First is the alloc<stat>() function. Since we’re writing C, sometimes it’s required to create C structs. And in C, memory is allocated explicitly, using malloc() function.

Second is the fact that we’re using memScoped {} block. Since stat is C struct, there’s no GC on it, and it must be deallocated manually. Using memScoped {} block will address that.

Now to the meat of our logic: copyAside() method:

Since copyfile() function requires a specific struct, we’re allocating and deallocating it manually using copyfile_state_alloc() and copyfile_state_free(state)

Last thing we need to cover is how we generate names for new files. This is simply some nice Kotlin code:

It’s a very naive piece of code, which relies that file that we watch has an extension, and totally neglects the fact that there are files from previous runs. But it should do for the demonstration purposes.

Now, how do we run it?

One option is to use CLion, of course.

But let’s use command line instead, to understand a bit more about the process:

./gradlew build && ./build/konan/bin/macos_x64/file_watcher.kexe ./README.md 1

So, first we run gradlew build

And you should see something like:

BUILD SUCCESSFUL in 16s

Ouch. Unlike Kotlin projects for the JVM or Go code, it takes quite a lot of time to compile a very modest Kotlin Native project.

Anyway, you should see some dots running in your console. And if you change README.md file, you should see something like this:

................................
File copied: ./README.1.md
...................
File copied: ./README.2.md

It’s hard to measure startup time of this application, but we can measure memory usage with OSX Activity Monitor: 852KB.

Nice!

Conclusions

So, with Kotlin Native we can get single executable binary, with a small memory footprint. Perfect? Almost.

If you’ve been following my posts, you know I’m a big fan of testing everything. Go has a very nice out-of-the-box testing framework, for example. Kotlin Native? Not yet.

Apparently there were some attempts by JetBrains to tackle this in 2017, but since even the official examples don’t have unit tests, I assume those didn’t bear fruit yet.

Another point is cross-platform support. At the time, Native experience is very barebones.

For example, as you’ve probably noticed, I’ve been running all those examples on OSX. If you try to compile the project on Windows, you’ll fail, since copyAside() and modified() methods depend on platform.darwin import, which is available only on OSX. Hope that in the future, some wrappers will be added by the Kotlin team to abstract at least the file system operations.

In the next article on the topic I’ll cover how to fix those issues by making this project multiplatform, and add some tests in the process.

As usual, you can get all the code here:

https://github.com/AlexeySoshin/KotlinNativeFileWatcher

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Alexey Soshin

Solutions Architect @Depop, author of “Kotlin Design Patterns and Best Practices” book and “Pragmatic System Design” course

No responses yet

Write a response