Moderne Android-Entwicklung mit Kotlin

Kotlin verspricht eine gut lesbare und prägnante Syntax, moderne Sprachfeatures und funktionale Programmierung bei hoher Sicherheit. Lässt die Java-Alternative durch ihr nahtloses Zusammenspiel mit Android auch die Herzen der App-Entwickler höher schlagen?

Know-how  –  6 Kommentare
Moderne Android-Entwicklung mit Kotlin
Anzeige

Nach gut fünf Jahren der Entwicklung brachte JetBrains im Februar 2016 die lange ersehnte Version Kotlin 1.0 heraus und versprach Stabilität: Künftige Kotlin-Versionen sollen an bestehendem Code keine Änderungen mehr erfordern. Als statisch typisierte Allzwecksprache für JVM und Browser lässt sich Kotlin überall einsetzen, wo Java läuft.

Einen ersten Eindruck der prägnanten Schreibweise und zugleich guten Lesbarkeit demonstriert das folgende erweiterte "Hello World". Auch ohne Vorerfahrungen dürfte das Beispiel den meisten Java-Entwicklern verständlich sein.

Anzeige
import java.util.*

fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Publikum"
val zuschauer = Gast(name, title = Title.wertes)

println(zuschauer)
println("Hallo ${zuschauer.title} $name")
}

data class Gast(val name: String,
var zeit: Date = Date(),
val title: Title?)
enum class Title { Herr, Frau, wertes }

Das Beispiel zeigt einige Eigenschaften, die das Arbeiten mit Kotlin effizient gestalten. Dazu gehören die prägnanten Schlüsselwörter und optionalen Semikolons am Zeilenende oder die Verwendung von Ausdrücken in String Templates. Parameter können Default-Werte tragen und werden damit optional.

Als statisch und streng typisierte Sprache bietet Kotlin die bekannt gute Typsicherheit, erspart dem Entwickler aber unnötige Tipparbeit. Dank Type Inference erschließen insbesondere lokale Variablen ihren Typ selbst, sodass Typangaben üblicherweise nur an Schnittstellen notwendig sind.

Die Data-Klasse Gast ist ein einfacher, erweiterbarer Werte-Container. Kotlin übernimmt viel vom klassischen Boilerplate-Code wie Getter- und Setter-Methoden, Nullwertprüfungen, die equals(), hashCode() und toString()-Implementierungen sowie einzelne Komfortfunktionen. So lassen sich Data Classes etwa wieder komfortabel in ihre einzelnen Properties zerlegen.

var (zname, _, ztitel) = zuschauer

Die funktionsgleiche Java-Implementierung der dreizeiligen Data Class Gast bringt es dagegen auf stattliche 100 Zeilen.

Dass Kotlin-Code trotz seiner Kürze dennoch gut und intuitiv lesbar ist, wird in der Praxis durch die kurze Einarbeitungszeit deutlich. Routinierten Java-Entwicklern genügt oft etwas Basiswissen und der Wunsch nach eleganterem Code, um sich – geführt durch Compiler und IDE – einen Großteil der Kotlin-Funktionen zu erschließen.

Dort wo Intuition nicht mehr genügt, hilft ein Blick in die gelungene Dokumentation. Für die ersten Schritte oder das weiterführende Studium eignen sich die Kotlin Koans. Dieses Curriculum aus 42 Lerneinheiten können Interessierte direkt im Browser oder mit dem "Kotlin Koans"-Plug-in in der freien IntelliJ IDE (leider nicht in Android Studio) abarbeiten.

Wenn es um Kotlin im Kontext der Android-Entwicklung geht, zeichnet sich die Sprache sicher vor allem durch ihre Interoperabilität aus. Trotz der vielen, fortschrittlichen Sprach-Features begnügt sich Kotlin mit Java-6-Bytecode und ist damit im Gegensatz zu Java 8 uneingeschränkt mit allen Android-Versionen kompatibel.

Im Projekt lassen sich sowohl Java- und Kotlin-Bibliotheken als auch einzelne Klassen des Quellcodes in beiden Sprachen frei mischen und können ohne Einschränkungen aufeinander zugreifen. Über eine Konvertierungsfunktion in Android Studio können Entwickler Bestandsprojekte stufenweise nach Kotlin migrieren und so im direkten Vergleich erleben. Der nahtlose Zugriff in beide Richtungen hebt Kotlin deutlich von anderen JVM-Sprachen ab.

Aus dem selben Hause wie Kotlin stammt auch IntelliJ IDEA. Die IDE bildet die Grundlage für Googles offizielle Entwicklungsumgebung Android Studio. Zudem setzt Androids Build-System Gradle ab Version 3 auf Kotlin als zusätzliche Möglichkeit für die Build-Definitionen. Beides verspricht exzellente Tooling-Integration und Zukunftssicherheit.

Der Einstieg in die App-Entwicklung mit Kotlin gelingt denkbar einfach. Unter File|Settings | Plugins lässt sich Kotlin via caps]Install JetBrains plugin[/caps] in Android Studio installieren. Einen Neustart später bietet die Tastenkombination Strg + Shift + A unter dem Stichwort Kotlin die Aktion Configure Kotlin in Project an. Und schon können Entwickler Kotlin im Projekt einsetzen. Eine ausführlichere Anleitung findet sich in der offiziellen Schritt-für-Schritt-Anleitung.

Über die Aktion Convert Java File to Kotlin File lassen sich nun einzelne Java-Klassen automatisch nach Kotlin überführen. In komplexeren Klassen sind gelegentlich im geringen Umfang manuelle Nacharbeiten erforderlich. In der Regel sind diese der strikten Nullwert-Behandlung in Kotlin geschuldet.

Sicherheit, Properties & UI-Zugriff

Im Typsystem von Kotlin besitzt null gegenüber dem von Java einen Ehrenplatz: Kotlin-Typen wissen, ob sie Nullwerte enthalten können. Compiler und IDE fordern stringent die Berücksichtigung des Schnittstellenvertrags ein. So garantiert der Typ TextView stets einen Wert, während ViewGroup? auch Nullwerte haben kann und damit sowohl im Kontrollfluss als auch bei der Derefenzierung speziell zu behandeln ist:

var toolbar: ViewGroup? = null
var textView: TextView = TextView(baseContext)

Ungeprüfte Zugriffe oder Zuweisungen mit null-fähigen Typen wie in den folgenden Beispielen sind folglich nicht erlaubt und erzeugen Compiler-Fehler. An Schnittstellen zu Java erstellt Kotlin zudem transparent entsprechende Prüfungen beziehungsweise gibt sein Wissen über @NotNull-Annotationen weiter.

// Alle Statements erzeugen Compiler-Fehler
toolbar.measuredHeight
textView = toolbar.getChildAt(0) as TextView

Die Behandlung von Nullwerten versüßt Kotlin zudem mit vielen Abkürzungen. Unter Java wären für die eingeforderten Kontrollen folgender Code notwendig:

TextView child = null;
if (toolbar != null && toolbar.getChildAt(0) instanceof TextView) {
child = (TextView) toolbar.getChildAt(0);
}
textView = child != null ? child : new TextView(getBaseContext());

if (textView instanceof EditText) {
((EditText) textView).selectAll();
}

Der Elvis-Operator (?:) und der Safe-Call-Operator (?.) sind aus anderen Programmiersprachen bekannte Kurzschreibweisen. Eine Besonderheit von Kotlin ist jedoch, dass es vorangestellte Typprüfungen über die sogenannte Smart Cast berücksichtigt. Im Beispiel versteht der Compiler, dass innerhalb des if-Blocks textView vom Typ EditView sein muss und bewahrt den Entwickler vor einer erneuten Nullwert-Prüfung oder einer expliziten Typumwandlung. Trotz der zusätzlich eingeforderten Sicherheit liest sich die Variante mit Kotlin am Ende deutlich eleganter und prägnanter als das Original.

val child = toolbar?.getChildAt(0) as? TextView
textView = child ?: TextView(baseContext)

if (textView is EditText) {
textView.selectAll()
}

Aufmerksamen Lesern wird nicht entgangen sein, dass der Aufruf getBaseContext() in Kotlin zu einem einfachen baseContext geworden ist. Klassen in Kotlin können Properties besitzen, die implizit Getter-Methoden und gegebenenfalls zusätzliche Setter enthalten. Beide lassen sich auch überladen. Wie bei Variablen sind veränderbare Properties durch das Schlüsselwort var, nur lesbare Properties hingegen über val deklariert. Beim Zugriff auf Java-Code lässt Kotlin passende Getter/Setter-Paare als Properties erscheinen, was die eingangs erwähnte Kurzschreibweise erklärt.

Im Kontext von Android bringen Kotlins Properties einige weitere interessante Eigenschaften mit. Einen wahren Produktivitäts-Boost bieten etwa die synthetischen Properties der Kotlin Android Extensions. Entwicklern dürfte eine der größten Quellen für Boilerplate-Code unter Android bestens vertraut sein: Das stetig wiederkehrende Muster für den Zugriff auf die Views in den Activities oder Fragments über die findViewById()-Methode.

EditText editTitle = (EditText) v.findViewById(R.id.edit_title);
editTitle.setText(mItem.getTitle());

CheckBox enabledBox = (CheckBox) v.findViewById(R.id.enable_box);
enabledBox.setChecked(true);

Button createButton = (Button) v.findViewById(R.id.create_entry);
createButton.setOnClickListener(new OnClickListener() {
@Override public void onClick(View button) {
createElement();
}
});

Durch die Butter-Knife-Bibliothek gibt es auch für Java Ansätze, um sich Teile dieser Tipparbeit zu sparen. Kotlin geht noch einen Schritt weiter, und macht das Arbeiten mit UI-Elementen unter Android fast vollständig transparent. Die von Haus aus mitgelieferten Kotlin Android Extensions erzeugen zur Compile-Zeit automatisch für alle UI Widgets eines Layouts korrekt typisierte Properties in einem synthetischen Paket. Damit reduziert sich der Zugriff auf GUI-Elemente auf ein import-Statement und ihre direkte Verwendung als Objekte über ihre IDs.

import kotlinx.android.synthetic.main.activity_hello.*
// ...
edit_title.setText(mItem.title)
enable_box.isChecked = true
create_entry.setOnClickListener { createElement() }

Um in den Genuss dieses Sahnehäubchens zu kommen, genügt es, die zusätzliche Direktive apply plugin: 'kotlin-android-extensions' in app/build.gradle aufzunehmen. Danach lässt sich zu jeder Layoutdatei ein gleichnamiges, virtuelles Paket importieren.

Im Beispiel stellt das Paket kotlinx.android.synthetic.main.activity_hello den Zugriff auf alle in der Datei layout/activity_hello.xml definierten UI-Elemente bereit. Da die virtuellen Properties bereits den richtigen Typ aufweisen, entfällt mit den sonst notwendigen Typecasts eine weitere lästige Fehlerquelle.

Oftmals sind Werte unter Android beim Erstellen der Klasse noch unbekannt und beispielsweise erst beim onCreate()-Callback verfügbar. Die Deklaration sogenannter lateinit-Properties erlaubt es, Properties außerhalb des Konstruktors zu initialisieren. Somit muss das Programm nicht auf Nullwerte zurückgreifen, was eine volle Nullwertbehandlung bei jedem Zugriff erforderlich machen würden.

Auch die leistungsfähige Option, den Zugriff auf Properties über by an Dritte zu delegieren, bietet weitere Ansätze. Im Beispiel wird etwa die Initialisierung des Properties imm als Lambda-Ausdruck an lazy() und damit an den ersten Lesezugriff delegiert.

val lateinit id: String
val imm: InputMethodManager by lazy ( {
getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager } )

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
id = getString(R.string.app_name)
imm.showInputMethodPicker()
}

Bei allen Vorteilen – einen kleinen potenziellen Pferdefuß für Android haben die Kotlin-Properties jedoch: Da für öffentliche Attribute zusätzliche Getter- und Setter-Methoden zu generieren sind, stößt man gegebenenfalls schneller an das 64k-Methodenlimit der Android-DEX-Dateien. Bei Bedarf können Entwickler das jedoch mit lateinit oder @JvmField vermeiden.

Extension Methods, Funktionen & Beispiel

Die APIs von Android und Java sind auf einen breiten Einsatzbereich und größtmögliche Flexibilität ausgerichtet. Im Alltag spiegelt sich das häufig in langer und repetitiver Verwendungen wider. In der eigenen Android-App wünschen sich Entwickler schnell individuelle Abkürzungen für wiederkehrende Wege. Üblicherweise führt das schnell zu einem Potpourri unterschiedlicher Util-Klassen im Projekt.

Um das zu umgehen, erlaubt Kotlin ähnlich wie C# das Erweitern fremder Klassen um neue Funktionalität ohne von ihnen ableiten oder sie in ein Decorator Pattern verpacken zu müssen. Über Extensions Methods und Extension Properties lassen sich Java- und Android-Klassen mit neuen Methoden und Properties ausbauen.

fun Fragment.toast(msg: String,
dur: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this.getActivity(), msg, dur).show()
}

var EditText.stringValue: String
get() = text.toString()
set(str) { setText(str) }

val Int.dpInPixel: Int get() = (
this * Resources.getSystem().displayMetrics.density + 0.5f
).toInt()

Das Beispiel erweitert die Android Fragments um eine Methode für Toast-Meldungen, die EditText-Widgets um eine Property für den komfortableren Direktzugriff auf den aktuellen Text und Integer-Zahlen um eine Property für die direkte Umrechnung einer dp-Einheit in Pixel. Ihre Verwendung geht ähnlich elegant von statten:

toast("Hallo!")
nameEdit.stringValue = "Unbekannter"
val p = 5.dpInPixel

Bereits Kotlins Standardbibliothek rüstet über Extensions Funktionen in Java-Klassen nach. Neben eigenen Erweiterungen bieten Bibliotheken wie KAndroid und Anko, die sich speziell an Android richten, weitere hilfreiche Extensions an.

Funktionen an Events zu knüpfen gehört zum täglich Brot der App-Entwickler. Der dafür notwendige Code präsentiert sich in der traditionellen Form unter Android noch etwas sperrig.

view.setOnClickListener(new OnClickListener() {
@Override public void onClick(View view) {
view.scrollTo(0,0);
}
});

In neueren Android-Versionen oder mit Retrolambda lassen sich die auch als Lambda oder Closure bekannten anonymen Funktionsausdrücke nutzen, um Interfaces mit nur einer fehlenden Methode zu implementieren (Single Abstract Method). Dem Ansatz folgt auch Kotlin, sodass sich der Click-Listener in der folgenden Form implementieren lässt:

view.setOnClickListener { it.scrollTo(0,0) }

Das Symbol it ist in Kotlin ein eingebauter Platzhalter. Er kann immer verwendet werden, wenn der Funktionsausdruck nur einen Parameter erwartet. Für Funktionen mit mehreren Parametern kommt die vermutlich etwas vertrautere Syntax { x,y -> x + y } zum Einsatz. Ist ein Lambda-Ausdruck der letzte Parameter eines Funktionsaufrufs, können die runden Klammern entfallen. Das vorige Beispiel ist somit unter Kotlin nur syntaktischer Zucker für die ausgeschriebene Form:

view.setOnClickListener ({ view -> view.scrollTo(0,0) })

Kotlin geht aber über die aus Java 8 bekannten Lambdas hinaus, da Funktionen in der Sprache Elemente erster Klasse sind. Sie können sowohl als Variablen, Properties, Parameter und Rückgabewerte genutzt werden. Dadurch erlauben sie eine Verschachtelung von Funktionsausdrücken, die allgemein als Funktionen höherer Ordnung bekannt sind.

Die folgende Funktion erwartet als Parameter eine andere Funktion und führt sie nur aus, wenn die aktuelle Android-Version mindestens Nougat ist:

fun ifNougatOrNewer(f: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
f()
}
}

Dank der Kurzschreibweise lässt sie sich ohne runde Klammern nutzen.

ifNougatOrNewer {
showVRButton()
}

Ein Großteil der Kotlin Standard Library ergänzt lediglich die vorhandenen Java-Klassen durch Extensions um Funktionen höherer Ordnung. Sie rüsten oft fortschrittliche, aber gemeinhin übliche Funktionen wie filter() nach.

val list = listOf("a", "b", "c", "d")
for (index in list.indices.filter { it % 2 == 0 }) {
println("$index -> ${list[index]}")
}

Zum Abschluss noch ein kleines Beispiel für einen praxisnahen Eindruck. Bis auf das im Layout-Editor leicht erstellbare Layout-XML, handelt es sich um eine vollständig in Kotlin geschriebene Android-App. Neu ist noch der when-Ausdruck als flexibeler Ersatz für den switch-Operator. Außer konstanten Werten können Entwickler in when auch Ausdrücke (isValid(id)), Bereiche (in 3..7) oder Typprüfungen (is String) verwenden.

package de.bentolor.helloworld

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.EditText
import kotlinx.android.synthetic.main.activity_hello.*
import java.util.*

class HelloActivity : AppCompatActivity() {

private val gast = Gast(name = "Publikum")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hello)
modelToView()

editName.setOnEditorActionListener { v, i, e ->
gast.name = editName.inhalt()
modelToView()
false
}
anredeGroup.setOnCheckedChangeListener({ g, id ->
gast.titel = when (id) {
radioHerr.id -> Titel.Herr
radioFrau.id -> Titel.Frau
else -> Titel.wertes
}
modelToView()
})
}

fun modelToView() {
labelGruss.text = gast.anrede
}
}

enum class Titel { wertes, Herr, Frau, Doktor }

data class Gast(var titel: Titel = Titel.wertes,
var name: String, val geburt: Date? = null) {
val anrede: String
get() = "Hallo $titel $name!"
}

fun EditText.inhalt(): String = text.toString()

Ein genauerer Blick auf Kotlin als Alternative für die Android-Entwicklung lohnt sich. Neben der zusätzlichen Sicherheit und Prägnanz lockt die gut in Android Studio integrierte Programmiersprache mit vielen Verbesserungen und neuen Ausdrucksmöglichkeiten, die Entwickler im Alltag schnell nicht mehr missen möchten. Obwohl der vorliegende Artikel nur einen ersten Überblick geben konnte und es noch deutlich mehr zu entdecken gibt, bleibt Kotlin-Code in der Regel stets gut lesbar und leicht verständlich.

Dabei machen sich insbesondere die kleinen Helferlein überraschend angenehm bei der Entwicklungseffizienz bemerkbar. Neben dem komfortablen Zugriff auf UI-Widgets über die Kotlin Android Extensions bringt die Standardbibliothek viel neuen Komfort mit. Die oft nur einzeiligen Extensions der 736 KByte schlanken Runtime erleichtern viele alltägliche Aufgaben und lassen einzelne Utility-Bibliotheken überflüssig werden.

Die Funktionen höherer Ordnung der Kotlin Standardbibliothek sind für die Inline-Kompilierung markiert, sodass ihr Einsatz in der Praxis keinerlei zusätzlichen Overhead gegenüber einer manuellen, sequenziellen Implementierung nach sich zieht. Das ist insbesondere für Android interessant, erlaubt es doch die Ausdrucksfähigkeit Scala-ähnlicher Konstrukte, ohne die sonst üblichen Einbußen bei der Laufzeit hinnehmen zu müssen. Mit Kovenant, KAndroid, Anko und Fuel hat das Kotlin-Ökosystem weitere interessante Bibliotheken zu bieten, die sich speziell an Android-Entwickler richten.

Die Kombination aus Kotlin und Android hinterlässt inzwischen einen stimmigen und ausgereiften Eindruck und bildet nicht zuletzt aufgrund des nahtlosen Zusammenspiels mit Java-Code ein attraktives Gesamtpaket. Wo immer es sich anbietet, greift der Autor daher inzwischen gern zu Kotlin und lädt dazu ein, es ihm in den eigenen Experimenten gleich zu tun. Happy Koding! (jul)

Benjamin Schmid

betreut als Technology Advisor seine Kollegen bei der eXXcellent solutions GmbH in allen technologischen und methodischen Fragestellungen. Seine praxisnahen Erfahrungen und Aha-Momente rund um Java, Web und .NET gibt er dabei immer wieder gerne weiter.

Siehe dazu auch

Einführung in die Programmierung mit Kotlin, PDF, 7,99 Euro

Anzeige