Kotlin Multiplatform

Article JAVA Magazine 04 -2021

 

Kotlin is een krachtige, moderne taal die de laatste jaren veel aan populariteit wint. In Kotlin geprogrammeerde software draait zonder problemen op een JVM, maar vertaalt ook makkelijk naar JavaScript en native code (bijvoorbeeld iOS, macOS of Linux). Kotlin is dus geschikt voor het bouwen van zowel front-end, als back-end, als mobiele applicaties.

 

Overstappen op Kotlin is erg eenvoudig, omdat het volledige interoperabiliteit heeft met Java. De syntax is bovendien herkenbaar, want het is gebaseerd op populaire talen als Java, C# en TypeScript. Kotlin kent een uitgebreid ecosysteem en heeft geweldige ondersteuning in IntelliJ.

 

In dit artikel laat ik zien hoe je een multi-platformproject in Kotlin maakt en hoe elegant Kotlincode eruit ziet.

 

{ Geschiedenis }

Miljarden regels bestaande code moeten blijven werken als er nieuwe features in een programmeertaal worden gelanceerd. Langer bestaande programmeertalen ontwikkelen zich daardoor vaak langzaam, omdat zij een grote nalatenschap (legacy) in de wereld hebben.

 

Dit maakt het programmeren in bestaande talen (bijvoorbeeld Java) in een aantal opzichten steeds minder aantrekkelijk. Dit geldt niet voor Kotlin. Kotlin is een vrij jonge taal die enorm aan populariteit wint. Veel wat in Java nog niet kan en misschien ook nooit gaat komen, kan wel in Kotlin. Voorbeelden zijn ‘pattern matching’ en ‘coroutines’. Kotlin is een soort ‘Java on steroids’, maar wel simpel, beknopt en elegant, in tegenstelling tot bijvoorbeeld Scala (naar mijn mening).

 

Hoe is deze taal ontstaan? Kotlin is open source en wordt gemaakt door JetBrains, de ontwikkelaar van onder andere IntelliJ. IntelliJ 1.0 stamt uit 2001 en is zelf in Java gebouwd. De ontwikkelaars liepen rond 2010 echter tegen de grenzen van Java aan. Ze waren bovendien stiekem best een beetje jaloers op C#.

 

Het was echter geen optie om de bestaande codebase in Java weg te gooien. Een nieuwe taal moest dus volledig integreren met de bestaande code. Hiervoor was nog geen goede oplossing, dus werd besloten om zelf een nieuwe taal te ontwikkelen. Ze wilden een taal met een compacte syntax, statisch getypeerd, makkelijk om te leren en waarin ontwikkelaars productief zijn en vooral plezier hebben. Voilà: Kotlin werd geboren in 2016. Op het moment van schrijven is versie 1.5 de meest recente.

 

{ Target? }

Als je over Kotlin leest, kom je al snel de term ‘target’ tegen. Daarmee wordt bedoeld: in welke doelomgeving ga je de software uitvoeren? Als Javaan ben je niet gewend om daarin een keuze te moeten maken. Zoals je weet worden in Java geschreven sources naar classes vertaald en die draaien op een JVM.

 

Java wordt tegenwoordig vooral nog gebruikt voor de back-end. Voor de front-end worden vaak JavaScript-frameworks zoals Angular en React gebruikt. Daardoor moet je als full-stackontwikkelaar meerdere talen, frameworks en paradigma’s leren. Wel leuk, maar voor de kwaliteit en productiviteit niet altijd optimaal.

 

In Kotlin geschreven software kent als targets, naast de JVM, ook JavaScript (browser of Node.js), Mobile (Android of iOS) en Native. Bij het aanmaken van een nieuw project in IntelliJ zie je deze keuzes ook terug (zie afbeelding 1).

 

Afbeelding 1.

 

Een Kotlinproject kun je in principe laten managen door zowel Maven als Gradle, maar alleen voor de JVM-target kun je Maven kiezen. Voor alle andere targets heb je Gradle nodig. Bovendien is de ondersteuning voor Kotlin in Gradle beter. Ik ga in dit artikel dan ook uit van Gradle.

 

Om je een idee te geven hoe dit werkt, volgen nu twee voorbeelden.

 

{ JVM }

Bij Kotlin/JVM vertaalt de Kotlin compiler je sources naar classes (zoals je gewend bent in Java). De Kotlin runtime library (een jar van slechts 1.3 MB) is nodig op de runtime-omgeving en kan worden meegecompileerd in je jar (zie afbeelding 2).

 

Afbeelding 2.

 

Zo’n project kent maar weinig extra configuratie. Je hoeft alleen de kotlin.jvm-plugin als plugin en de Kotlin library als dependency op te nemen:

 

 

plugins {
id ‘org.jetbrains.kotlin.jvm’ version ‘1.4.30’
}

 

dependencies {
implementation “org.jetbrains.kotlin:kotlin-stdlib”
}

 

{ JavaScript }

Voor Kotlin/JS is dit vergelijkbaar, behalve dat de JVM runtime library nu natuurlijk niet nodig is en de plugin nu ‘js’ heet. Daarnaast moet je de Kotlin/JS target declareren en initialiseren:

 

plugins {
id ‘org.jetbrains.kotlin.js’ version ‘1.4.30’
}

kotlin {

      js { browser { binaries.executable() } }
}

 

Om je een idee te geven van de sources en de target files, volgt een simpel voorbeeld. Als je een Hello World app maakt, krijg je de sources uit afbeelding 3.

 

Afbeelding 3.

 

simple.kt

 

import kotlinx.browser.document

 

fun main() {
console.log(“Hello, world!”)

    document.getElementById(“header”)?.innerHTML += “Check the console…”

}

 

index.html

<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>Hello</title>
<script src=”helloworld.js” defer></script>
</head>
<body>
<h1 id=”header”></h1>
</body>
</html>

 

Alle code uit de main-functie wordt met behulp van de kotlin.js-plugin omgezet in helloworld.js, die in index.html wordt uitgevoerd. Het enige beetje pure html dat je in je project vindt is index.html. De rest van je front-end schrijf je in Kotlin. Daarover later meer. Het grote voordeel is dat deze code wordt gecompileerd. Als daar fouten in zitten merk je dat al in een vroeg stadium.

 

N.B.: wist je dat types in Kotlin standaard non-null zijn? Dat betekent dat de waarde van een variabele van dat type niet null mag zijn. Hierdoor kun je altijd veilig de waarde opvragen via een referentie, zodat je nooit een NullPointerException krijgt. In bovenstaand voorbeeld in simple.kt is het type van getElementById echter wél nullable, omdat de header mogelijk niet in de html-template gedefinieerd is. Daarom moet je de safe call operator ?. gebruiken om de waarde te krijgen. Op deze manier wordt het statement alleen uitgevoerd als de reference niet null is.

 

Na het uitvoeren van het commando gradle assemble genereert Gradle met behulp van webpack de distribution (afbeelding 4).

 

Afbeelding 4.

 

Zoals je ziet in afbeelding 4 is het een echte Single Page Application (SPA) geworden: er is één html-pagina en het gedrag wordt volledig client side (in de browser) gestuurd met behulp van JavaScript.

 

Voer je nu gradle run –continuous uit, dan wordt er een webserver opgetuigd die de webapp toont in de browser. Bij aanpassingen vindt automatisch een reload plaats (Afbeelding 5).

 

Afbeelding 5.

 

{ Multiplatform }

Bovenstaande applicaties kenden één target. Als we software schrijven voor een combinatie van targets, met een deel gezamenlijke code, dan kiezen we voor een multiplatformproject. Hierbij kun je denken aan één applicatie die voor zowel Android als iOS gebouwd wordt. Een ander voorbeeld is een full-stack webapplicatie, met Kotlin/JS voor de front-end en Kotlin/JVM voor de back-end. Ik ga nu even uit van dit laatste voorbeeld.

 

Naast de platformspecifieke code bevat een multiplatformproject een gedeelde library voor alle onderdelen. In die gedeelde library schrijf je eenmalig de code voor je domein, de businesslogica, code voor http, serialisatie, etc. Als delen van deze code een specifieke implementatie hebben voor een platform, declareer je die functie in common als expect, waardoor je hem in alle targets een actual implementatie moet geven. Dit wordt ‘at compile time’ gecheckt (zie afbeelding 6).

 

Afbeelding 6.

 

De specifieke configuratie van een multiplatformproject ziet er heel herkenbaar uit:

 

plugins {
id ‘org.jetbrains.kotlin.multiplatform’ version ‘1.4.30’
}

kotlin {

      js { }

      jvm { }

sourceSets {

            commonMain { … }

            jvmMain { … }

            jsMain { … }

            …

      }

}

 

Als plugin gebruiken we nu de multiplatformplugin. We definiëren de targets en omdat we meerdere source sets hebben die niet op de default locatie staan, declareren we die ook. Een Gradle source set definieert welke sources en resources er zijn, het classpath inclusief dependencies en de output-locatie.

 

{ Front-end code structureren }

Bovenstaand Kotlin/JS-voorbeeld is nogal simpel. In de main wordt de DOM bevraagd en beïnvloed. Als je een complexe front-end gaat bouwen, werkt dit natuurlijk al snel niet lekker meer voor onderhoud en de leesbaarheid.

 

Voor het bouwen en structureren van SPA’s hebben we al een aantal jaren verschillende JS-frameworks en libraries, zoals jQuery, Vue en Angular. Kotlin/JS is vriendjes geworden met React. Hiermee kunnen we goed gestructureerde webapplicaties bouwen, gebruikmakend van herbruikbare componenten, een groot ecosysteem en door de gemeenschap gemaakte componenten. De vraag is nu wel: hoe bouwen we een webpagina in Kotlin/JS met React? Het antwoord: met Kotlin DSL’s. Daarover vertel ik je graag meer in een volgende editie van het Java Magazine!

 

{ Het grote voordeel }

In dit artikel heb je gezien hoe krachtig en elegant Kotlin is en welke platforms je ermee kunt targetten. Het grootste voordeel van Kotlin Multiplatform is dat je code maar één keer hoeft te schrijven (in Kotlin) en je deze vervolgens op meerdere platformen kan gebruiken en daarin een specifieke implementatie kunt geven. Vooral wanneer je software maakt die veel logica deelt tussen platformen kan dit tijd en moeite besparen. Met Kotlin kun je alles waar je normaal meerdere talen, frameworks en tooling voor nodig hebt. One language to rule them all!

 

Referenties

  1. Kotlin in Action – Dmitry Jemerov and Svetlana Isakova – ISBN 9781617293290
  2. Kotlin Hands-On: https://play.kotlinlang.org/hands-on/
  3. Introduction to Kotlin Multiplatform
  4. Building a Full Stack Web App with Kotlin Multiplatform
  5. Building Web Applications with React and Kotlin/JS
  6. org
  7. Multiplatform programming: https://kotlinlang.org/docs/multiplatform.html
  8. Kotlin Multiplatform: https://kotlinlang.org/docs/mpp-intro.html

 

Bio:

Bram Janssens is trainer bij Info Support. Hij houdt ervan om zijn kennis met veel enthousiasme over te dragen. Plezier in het vak vindt hij het belangrijkst!