eXXcellent solutions GmbH Logo

Moderne Android-
Entwicklung mit Kotlin

2017-01-07
Benjamin Schmid
Technology Advisor
? for help
Kotlin für die moderne Android-Entwicklung by Benjamin Schmid is licensed under a Creative Commons License (BY-NC-SA/4.0) . Creative Commons Lizenzvertrag

Warum voranschreiten
in der Android-Entwicklung?

Android = Java 6 (teilweise)
keine Streams, Lambdas, try-with…– aber: Retrolambda, RxJava
Android API – Interitance Party, Nullwerte
Performance first
Java – längliche Idiome & ungenutzte Chancen
(für Kurzschreibweisen)

Kompromisse bei der Sicherheit
(Raw Types / Arrays, Nullwerte)
## Was ist Kotlin?

Russische Insel

im finnischen Golf, 32km vor Petersburg

statisch typisierte
Allzweck-Sprache
für JVM und Browser

## Prägnanz ## Sicherheit ## Lesbarkeit ## Interoperabilität ## Tooling

Datenblatt

Freie Software

APL 2.0 – IDE, compiler, libs, build tools

backed by JetBrains

20 Vollzeit & Community
„dogfooding“: 0,5 Mio. LOC

Stabil

Start: ~2010, v1.0 Feb. '16
Long-term backward compatibility

Integrationen

IntelliJ, Eclipse
Maven, Ant, Gradle

Prägnanz & Lesbarkeit

Schneller an's Ziel!

Prägnanz vs. Lesbarkeit

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} ${zuschauer.name}")
}

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

← Optional semicolons

← Top-level functions

← Kein new,
     named Params

← String interpolation



← Default values
valpublic final

Type inference

```kotlin fun noTypeInfer(vals: List<Int>): Unit { val distinct: Set<Int> = vals.toSet() val first: Int = vals[0] val negs: List<Int> = vals.filter( fun(n: Int): Boolean { return n < 0 } ) } ```
```kotlin fun typeInfer(vals: List<Int>){ val distinct = vals.toSet() val first = vals[0] val negs = vals.filter( fun(n) = n < 0 ) } ```

Typangaben üblicherweise nur an Schnittstellen erforderlich

Data Classes

```kotlin data class Gast( val name: String, var zeit: Date = Date(), val title: Title? ) ```

Werte-Container

- Um Methoden erweiterbar - Vererbung ab Kotlin 1.1
###### Kotlin übernimmt
- Getter & Setter - `equals()`
- `hashCode()` - `toString()`
- `copy()` für Varianten - `componentN()` für Destructuring

... exakt dasselbe mit Java

public final class Gast {
  @NotNull
  private final String name;
  @NotNull
  private Date zeit;
  @Nullable
  private final Title title;

  @NotNull
  public final String getName() {
    return this.name;
  }

  @NotNull
  public final Date getZeit() {
    return this.zeit;
  }

  public final void setZeit(@NotNull Date date) {
    checkParameterIsNotNull(date, "<set-?>");
    this.zeit = date;
  }

  @Nullable
  public final Title getTitle() {
    return this.title;
  }

  public Gast(@NotNull String name,
              @NotNull Date zeit,
              @Nullable Title title) {
    checkParameterIsNotNull(name, "name");
    checkParameterIsNotNull(zeit, "zeit");
    this.name = name;
    this.zeit = zeit;
    this.title = title;
  }>
  public Gast(String string, Date date,
              Title title, int n) {
    if ((n & 2) != 0) {
      date = new Date();
    }
    this(string, date, title);
  }

  @NotNull
  public final String component1() {
    return this.name;
  }

  @NotNull
  public final Date component2() {
    return this.zeit;
  }

  @Nullable
  public final Title component3() {
    return this.title;
  }

  @NotNull
  public final Gast copy(@NotNull String name,
                         @NotNull Date zeit,
                         @Nullable Title title) {
    checkParameterIsNotNull(name, "name");
    checkParameterIsNotNull(zeit, "zeit");
    return new Gast(name, zeit, title);
  }

  public String toString() {
    return "Gast(name=" + this.name +
           ", zeit=" + this.zeit +
           ", title=" + this.title + ")";
  }
  public int hashCode() {
    String s = this.name;
    Date d = this.zeit;
    Title t = this.title;
    return ((s != null ? s.hashCode() : 0) * 31
          + (d != null ? d.hashCode() : 0)) * 31
          + (t != null ? t.hashCode() : 0);
  }

  public boolean equals(Object object) {
    if (this == object) return true;

    if (!(object instanceof Gast))
        return false;

    Gast gast = (Gast)object;
    if (!areEqual(this.name, gast.name)
        || !areEqual(this.zeit, gast.zeit)
        || !areEqual(this.title, gast.title))
        return false;

    return true;
  }
}

… 3 Attribute!

Properties

  • Klassen in Kotlin kennen keine Felder, nur Properties
  • Compiler generiert Getter- & Setter sowie Backing Field
  • Properties of Android Intent class getX()/setX()-Paare
    aus Java erscheinen
    als Property x
  • Properties unterstützen Delegation

Ausdrücke & Konventionen

Mehr Ausdrücke (if, ?:, ?., …)
Pattern matching (when)
String Templates
Syntactic Sugar

listOf(…), repeat(3){…},
with(x){…}, x.apply{…}, …

public ist Standard
kein new; ; optional
Operatoren & Dekomposition über Namen

Ausdrucksstärke

Standard-Bibliothek

Konventionen

Idiome

##### `if` ist ein Ausdruck ``` fun isEven(i: Int) = if (i.mod(2) == 0) "gerade" else "ungerade" ``` ##### `with( … ) { }`: Parameter wird im Block zum `this` ``` private fun toJSON(aha: Aha): JSONObject { with(JSONObject()) { put(JSON_ID, aha.id.toString()) put(JSON_TITLE, aha.title) put(JSON_USEFUL, aha.isUseful) return this } } ``` Varianten: `apply()`, `let()` und `use()` als `try-with`-Equivalent

Konventionen - Operatoren

Feste Menge & Reihenfolge an Operatoren (vs. Scala). Diese werden über Namenskonventionen gemapped:
`+` → `.plus()` `-` → `minus()` `..` → `.rangeTo()` `[x]` → `get(x)`

Sicherheit

First-class immutables

Nur-lesbare Variablen aufwandsfrei durch val statt var

Standard-Collection Interfaces (List, Set, …) sind read-only

Zum ändern muss man z.B. MutableList statt List nutzen

Klassen & Methoden sind final by Default

open class ExtendableClass {
    open fun overridableMethod() { … }
}
Mutige Entscheidung!
… Framework-Entwickler müssen Acht geben!

The
billion-dollar
mistake

null und seine Folgen

Nullwert-Sicherheit

Nur explizit Nullwert-fähige Typen akzeptieren null.
Ungeprüfte Zugriffe führen zu Compile-Fehlern!

```kotlin var nullable: String? = null var nonNullable: String = "Hi!" // Ab hier compiliert nichts mehr … nonNullable = nullable nonNullable = null nullable.isEmpty() ```

Nullwerte sind
Bestandteil des Typsystems!

An den Schnittstellen baut Kotlin Prüfungen & @NotNull Annotationen ein.

Effizienter Umgang mit Nullwert-Typen

Effizienter Umgang mit Nullwert-Typen

Kotlin Icon Google Android Logo

Kotlin
& Android

Kotlin Icon + Google Android Logo – Ein Dream-Team?

  • effiziente & fehlerarme Entwicklung – wie vielfältig illustriert
  • konzipiert für den industriellen Einsatz
  • erzeugt Java 6 Bytecode – und damit perfekt Android-kompatibel
  • 100% Java Interoperabilität – in beide Richtungen ermöglicht …
  • klassenweises Mischen von Java & Kotlin-Code
  • Tooling-Heimspiel – Android Studio & Gradle 3 setzen auf Kotlin
  • Geringe Runtimegröße & -overhead – ca. 736KB in 1.0.6

1. Android Studio Plugin installieren

Install Kotlin Android Studio Plugin

2. Kotlin im Projekt aktivieren

Enable Kotlin in Android projects

3. Kotlin Extensions aktivieren (optional)

Add Kotlin Extensions to Gradle Build

Android on speed

Typischer Android-Boilerplate …

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();
    }
});

… der Zugriff auf die Views in den Activities oder
Fragments über die findViewById()-Methode:

Virtuelle Properties
für UI-Elemente

Das Kotlin Android Extensions erzeugt virtuelle mylayout.xml-Pakete

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

Ein einfacher Import erlaubt typsicheren Zugriff auf alle
darin enthaltene View-Elemente direkt als simples Property.
Ohne zusätzlichen Code, Annotations oder Runtime!

Lambda-Ausdrücke

Lambdas – Syntactic sugar

view.setOnClickListener({ view -> doSomething() })

Nur ein Lambda-Parameter? x -> … kann durch it ersetzt werden

view.setOnClickListener({ doSomething(it) })

Lambda ist letzter Aufrufparameter? Runde Klammern optional.

view.setOnClickListener { doSomething(it) }

Extension methods / properties

  • … erlauben das Erweitern fremder Klassen (Android, JRE)
  • this zeigt auf das erweitertete Objekt
// Extension method  für ein Android Fragment
fun Fragment.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
  Toast.makeText(this.getActivity(), message, duration).show()
}

// Extension property für das Android EditText Widget
var EditText.stringValue: String
    get() = text.toString()
    set(str) { setText(str)  }
// Verwendung
toast("Hallo Zuschauer!")
nameField.stringValue = "Max Muster"

Utilities „frei Haus“

Zahlreiche globale Funktionen und Extensions sind in der Kotlin Standardbibliothek bereits enthalten. Sie erweitern bestehende Klassen und Interfaces
und sind komfortabel über Code-Completion zugänglich.
List<String> list = Arrays.asList("a","b","c");
for (int index = 0; index < list.size(); index++) {
    if (index % 2 == 0)
        System.out.println(index + " -> " + list.get(index));
}
Sie helfen wesentlich beim prägnanten, dennoch intuitiv lesbar Code.
val list = listOf("a", "b", "c")
for (index in list.indices.filter { it % 2 == 0 }) {
    println("$index -> ${list[index]}")
}
Im Beispiel: listOf, indices, filter. Auch Bibliotheken für Android (Anko)

Tooling

Fokus: Tooling

Automatische Konvertierung
Java → Kotlin

![Convert Java Classes](img/convert-java.png) ![Convert Java Code from Clipboard](img/kotlin-copypaste-convert.png)

Demo

Hello Android: Layout

Hello Android: Code part #1

Hello Android: Code part #1

Hello Android: Code part #2

Hello Android: Code part #1

Kotlin Icon + Google Android Logo – Drawbacks

Die (wenigen) Schattenseiten

64K Methoden Limit
  • Dalvik Limit: 65.536 Methoden pro DEX file.
    → Jedes Kotlin Properties = +2 Methoden
    → Abhilfe: Multidex & @JvmField
Overhead (gering)
  • IDE: Performance der Auto-completion (minimal)
  • APK Größe: ~736KB & Checks
  • Compilerlaufzeit im ersten Durchgang (~15%)
Auf Java abzielende Frameworks
  • Schöne API umständlicher; besser Java nutzen

Android & null – Typische Konstellation

Werte für Initialisierung erst im Android-Callbacks bekannt … ```kotlin class EditPersonActivity : FragmentActivity() { var person: Person var anrede: String var imm: InputMethodManager … override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val personId = savedInstanceState?.getInt("value",0) person = PersonManager.load(id) anrede = resources.getString(R.string.anrede) imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager } } ```
So nicht möglich!

Lösungen: Verzögerte Initalisierung

##### `lateinit` Properties (empfohlen) ```kotlin lateinit var person: Person ```
##### Delegation des Properties ```kotlin var anrede: String by Delegates.notNull() ```
##### Lazy-Initalisierung ```kotlin val imm: InputMethodManager by lazy { getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager } ```
Wirft sprechende Exception, falls ohne Initialisierung gelesen wird!

Kotlin Icon – Advanced

Funktionen als
first-class elements

Funktionen sind Sprachelemente erster Klasse und können daher auch als Variablen, Properties, Parameter und Rück­gabe­werte genutzt werden.

Durch Verschachtelung lassen sich Funktionen höherer Ordnung (Higher-order functions) erschaffen

Higher-order functions – Beispiel

class FirstClassFunction(
        val f1: (String) -> Int,
        val f2: (Int) -> Boolean) {

    fun strToBool(str: String): Boolean {
        val f: (String) -> Boolean = higherOrderFun()
        return f(str)
    }

    private fun higherOrderFun(): (String) -> Boolean {
        return { x -> f2(f1(x)) }
    }
}

fun main(args: Array<String>) {
    val c = FirstClassFunction(
                Integer::parseInt,
                { it % 2 == 0 }
            )
    val strings = listOf("2", "7", "8")
    println(strings.filter(c::strToBool)) // Kotlin 1.1+
}

 
← Funktion als Parameter
 
 
 
← Funktion als Variable
 
 
 
← Funk. als Rückgabewert
Kombination via Lambda


 
 
 
← Funk.-Referenz
Lambda-Ausdruck
 
 
Was kommt raus?

Typsichere DSLs über „Empfänger“-Objekt

Es ist möglich, Funktionssignaturen zu definieren, die Lambda-Ausdrücke erwarten bei denen this auf Zielobjekte eines bestimmten Typs zeigen.

Darüber sind typsichere DSLs möglich:

```kotlin class HTML { fun body() { ... } } fun html(init: HTML.() -> Unit): HTML { val html = HTML() html.init() return html } html { body() } ```

 
 
 
 
← Erwartet Funktion mit this vom Typ HTML
← Erstellung des receiver object
← Führe Lambda auf receiver object aus



← Kurzform von html({…})
Lambda-Ausdruck für einen HTML-„Empfänger“

Anko: Android-DSL für XML

```kotlin relativeLayout { lparams(width = matchParent, height = matchParent) backgroundResource = R.drawable.background_activated checkBox { enabled = false gravity = Gravity.CENTER padding = dip(4) lparams(width = wrapContent, height = wrapContent) { alignParentRight() } } textView { textResource = R.string.aha_title rightPadding = dip(4) leftPadding = dip(4) lparams(width = matchParent, height = wrapContent) { leftOf(cb) } } } ```

Infix Notation

```kotlin infix fun Int.shl(x: Int): Int { … } 1 shl 2 // als Equivalent zu 1.shl(2) ```
1. Sind Member-Funktionen oder Extension Methods 2. Haben einen einzigen Parameter 3. Und sind mit dem `infix` Schlüsselwort markiert

Fazit

Im Browser ausprobieren

Kotlin Koans

http://try.kotl.in/

Quellen & Materalien

##### Materialien für den Einstieg * [*kotlinlang.org*](https://kotlinlang.org/) * [*Kotlin Koans*](https://github.com/Kotlin/kotlin-koans) auch online: [try.kotl.in](http://try.kotl.in) in IntelliJ: [Kotlin Edu Plugin](https://blog.jetbrains.com/kotlin/2016/03/kotlin-educational-plugin/) * [Ein Überblick über Java-Alternativen für den industriellen Einsatz](http://www.heise.de/developer/artikel/Ein-Ueberblick-ueber-Java-Alternativen-fuer-den-industriellen-Einsatz-2074554.html), Benjamin Schmid
##### Referenzen - [Sprachreferenz](http://kotlinlang.org/docs/reference/) ##### Verzeichnisse - [Awesome Kotlin](http://kotlin.link/) - [OSS Projects & Libraries](https://kotlinlang.org/docs/resources.html) ##### Diverses - [*@kotlin*](https://twitter.com/kotlin) & [*@bentolor*](https://twitter.com/bentolor) - [github.com/bentolor](https://github.com/bentolor)
##### Nennenswerte Frameworks - [KAndroid](https://github.com/pawegio/KAndroid) - [Fuel](https://github.com/kittinunf/Fuel) (HTTP f. Android) - [RxKotlin](https://github.com/ReactiveX/RxKotlin) (Reactive) - [spek](https://github.com/jetbrains/spek) (Testing) - [kovenant](https://github.com/mplatvoet/kovenant) (Android Promises) - [kotlinx.support](https://github.com/Kotlin/kotlinx.support) (JDK7/8 features) - [Klaxon](https://github.com/cbeust/klaxon) (JSON)
Bildnachweis: [Kotlin Islands Maps](https://commons.wikimedia.org/wiki/File:Kotlin_Island_1706.png), [Atomic bomb explosion](https://www.pond5.com/stock-footage/37592494/fireball-and-smoke-formation-out-atomic-blast.html), [Android Robot](https://commons.wikimedia.org/wiki/File:Android_robot.svg), [Kotlin Logo](https://resources.jetbrains.com/assets/products/kotlin/kotlin_logos.zip), [Hand-written arrows](http://designercandies.net/hand-drawn-arrows-brush/)