Test-Driving Kotlin in ZK
Let's see what kinds of benefits Kotlin offers over Java when working with ZK as your frontend framework in a Java EE application.
Join the DZone community and get the full member experience.
Join For FreeEvery now and then it's time to learn something new — especially for a software developer. Be it just to keep in touch with recent developments, to challenge yourself by getting out of your comfort zone, or to widen your perspective and get fresh ideas on something you already know.
Very often, this means trying out the latest framework or a new platform. Sometimes, you work on an existing project and several decisions are already set in stone.
Assume you work on a JEE application using ZK as the frontend framework running on the JVM. In this case, you might not have all the degrees of freedom you might like. Still, you can be cheeky and keep up to date by using an alternative programming language running on the JVM. Obviously, a major feature of this language should be the interoperability with existing Java code and libraries.
Kotlin claims to be compatible. So, I decided to put this to the test just to see how far I could get and what surprises it has to offer. (Mild spoiler: It works!) Other important features of Kotlin are shorter and more expressive syntax as well as a safer and more flexible type system, avoiding common mistakes.
As a positive side effect, learning Kotlin adds to your own skill set — for example, when it comes to coding for the Android Platform in a future project (you never know!).
So why not combine the pleasant with the useful and just...
Enable Kotlin
Adding the Kotlin compiler to an existing Maven/Gradle project is well-documented and offers no surprises (in this example I chose Gradle just because of its shorter syntax):
buildscript {
repositories { mavenCentral() }
dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}" }
}
apply plugin: "kotlin"
...
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib"
...
After that, .kt (Kotlin) files are compiled automatically and produce bytecode executable by the JVM. Existing Java code continues to compile as well.
The usual Gradle commands just work as before:
./gradlew compileKotlin
(just compile Kotlin)./gradlew test
(run all tests)./gradlew build
(e.g. build the WAR file)
That's all we need for now to move on.
Start Coding
Assume we have the following ZUL-Page, implemented using the MVVM pattern:
xxxxxxxxxx
<zk>
<div viewModel="@id('vm') @init('zk.kotlin.vm.MyViewModel')">
Your Name:
<textbox id="name"
value="@bind(vm.name)"
onOK="@command('submit')"/>
<button id="submit" label="submit"
onClick="@command('submit')"/>
<separator/>
<label id="response" value="@load(vm.response)"/>
</div>
</zk>
It will display a <textbox>
widget to enter your name. When pressing ENTER ("onOK") or clicking the button, it will trigger the "submit" command and display a response:
"Hello [yourname]!" in the <label>
below. What we need to implement is a View Model class with 2 properties: name
and response
.
In Java, this implementation could look like this:
xxxxxxxxxx
public class MyViewModel {
private String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponse() {
return String.format("Hello %s!", name);
}
"submit") (
"response") (
public void submit() {
}
}
Nothing remarkable here... just a POJO with a command handler - for the submit
command.
Shorter Code
As a first surprise, the same class in Kotlin becomes:
xxxxxxxxxx
class MyViewModel() {
var name = ""
val response get() = "Hello $name!"
@Command("submit")
@NotifyChange("response")
fun submit() {
}
}
One initially obvious benefit (from a Java developer's point of view) is the more compact/expressive syntax avoiding the (often brainlessly IDE-generated) getters and setters. Even in this embarrassingly trivial ViewModel class, we save about half the lines of code.
Astonishingly (not for a Kotlin developer) the compiled class still works in a ZK application when used as an MVVM ViewModel (without having to change the ZUL code or data- or command-binding expressions). Designed for interoperability with Java, the Kotlin compiler conveniently adds the getters/setters automatically, allowing existing frameworks to work as before, leveraging their functionality.
var
(line 2) stands for variable, hence it has an implicit getter and setterval
(line 3) is a read-only value, which needs just a getter
Where needed, we can implement a custom getter as for the calculated property response
(which, as a bonus, uses the string interpolation feature to insert $name
into the response string).
So far, we have simply achieved the same thing with less code (bad luck if you're paid by lines of code — which I think is just an urban legend anyway. Less typing for everyone else).
Safer Code
Other language features help to create code that is more compile/refactoring safe (without writing more code). Especially in a ZK+MVVM application, this can help to avoid runtime errors — often occurring due to a failed reflective method or field invocations.
In particular, I am referring those 2 annotations using magic strings that are resolved via reflection during runtime. As we can see in the code below, the command name and the notifyChange property name are such unwanted hard-coded String values:
xxxxxxxxxx
val response get() = "Hello $name!"
@Command("submit")
@NotifyChange("response")
fun submit() { }
The @Command method is used in the ZUL-File in a @command-binding:
xxxxxxxxxx
<button id="submit" label="submit"
onClick="@command('submit')"/>
To avoid repetition, the command name is optional in the @Command annotation and, if missing, matches the method name (this also works in Java but doesn't solve our problem entirely).
xxxxxxxxxx
@Command
@NotifyChange("response")
fun submit() { }
Still, if the method name changes due to refactoring, the UI will break at runtime — no compiler will help there. In order to be more flexible with refactoring changes, we can use Kotlin's nice reflection API to introduce a property value, which will always match the method name.
In a similar way, we can use the reflection API to avoid hard-coding the response
-property name (I'll get to that in a second). Here is the safer version of our View Model Class:
xxxxxxxxxx
class MySafeViewModel() {
val SUBMIT = ::submit.name;
var name = ""
val response get() = "Hello $name!"
@Command fun submit() {
notifyChange(::response.name)
}
}
... and the updated usage in the .zul file (mySafeIndex.zul):
xxxxxxxxxx
<button id="submit" label="submit"
onClick="@command(vm.SUBMIT)"/>
The syntax might look a bit alien at first — but hey, that's part of the learning experience. (Just try something similar in Java and you'll quickly learn to love this feature.)
As we can see, we traded the hard-coded Strings for compile- and refactoring-safe values, and we can use them in the ZUL file to never break the ZUL file due to command method name changes in the View Model.
Now, what happened to @NotifyChange("response")
? Unfortunately, we can't just say @NotifyChange(::response.name)
— that would have been nice, but annotation values require compile-time constants, while reflection is still a runtime API (both in Java and Kotlin).
Luckily, ZK has an (arguably ugly) utility method to trigger @NotifyChange programmatically ... We just have to call:
xxxxxxxxxx
BindUtils.postNotifyChange(null, null, this, "response")
//the same compile-/refactoring-safe using Kotlin's reflection API
BindUtils.postNotifyChange(null, null, this, ::response.name)
Still too much to write? Kotlin's Extensions help to reduce repetitive typing. On top, I'd argue that the first 2 parameters are null
99% of the time, so we can implement the following 2 extension functions, both doing the same thing:
xxxxxxxxxx
fun Any.notifyChange(propName: String) =
BindUtils.postNotifyChange(null, null, this, propName)
fun Any.notifyChange(prop: KProperty<*>) =
notifyChange(prop.name) /*calls the method above*/
The notifyChange
extension functions are defined on the Any type, so they can be called on every object. I can only recommend reading/understanding the documentation about this feature. Excessive use of this feature might make your code harder to reason about.
Here, it achieves a shorter syntax; fitting the existing ZK API to our needs. The following 4 lines have the same effect (you can choose which you like most):
xxxxxxxxxx
//using ZK's API
BindUtils.postNotifyChange(null, null, this, "response")
//using ZK's API + Kotlin reflection
BindUtils.postNotifyChange(null, null, this, ::response.name)
//using our extension function, passing the property name as string
notifyChange(::response.name)
//overloaded version using the property reference directly
notifyChange(::response)
You can also notify nested properties, e.g. the street
-property of an Address
object:
xxxxxxxxxx
//using ZK's API
BindUtils.postNotifyChange(null, null, someAddress, "street");
//shorter & compile safe using our extension
someAddress.notifyChange(Address::street)
Before I forget to mention it: In order to use Kotlin's reflection API, you have to add the kotlin-reflect dependency.
xxxxxxxxxx
compile "org.jetbrains.kotlin:kotlin-reflect"
Taking a Break
Since the article is already getting quite long, I'll cover a slightly more interesting CRUD example using ZK's Form-Binding in combination with Kotlin's data-classes and Kotlin+JUnit+ZATS in separate articles.
Special thanks go to Carsten Lenz (Germany)
who patiently helped out during a "quick" 20-30-minute call
(which actually took 2 hours)
Example Code
The full code is available on GitHub and can be run using the following commands:
xxxxxxxxxx
git clone https://github.com/zkoss-demo/zk-kotlin.git
cd zk-kotlin
./gradlew startJettyRunner
Opinions expressed by DZone contributors are their own.
Comments