Kotlin Meets Android

Mit Kotlin die Android-App-Entwicklung vereinfachen

Jan-Lukas Else

1. Kontext

Überblick über Kotlin

2. Funktionen und Syntax

Attribute

Java

String a;
int count = 0;
CoordinatorLayout coordinator;

Kotlin

private var count = 0
private var dialog: AlertDialog? = null
private lateinit var coordinator: CoordinatorLayout
private val toolbar: Toolbar by lazy { findViewById<Toolbar>(R.id.toolbar) } 
private val fab: FloatingActionButton by bind(R.id.fab)

Achtung!

Nicht alle val sind unveränderlich, aber schreibgeschützt

Beispiel:

val number: Int
    get() = Random().nextInt()

Empfehlung: Eine fun-ktion benutzen

fun getRandomNumber() = Random().nextInt()

Getter und Setter überschreiben

var isSomething: Boolean = true
var isSomething: Boolean
    get() { 
        doAnotherThing()
        return field
    }
    set(value) {
        doSomething()
        field = value
    }

Null-Sicherheit

Typen sind standardmäßig nicht null

Type? ermöglicht null

Null-Attribute benutzen:

Explizierter Aufruf mit !! (Nicht empfohlen)

something!!.doSomething()

Sicherer Aufruf mit ?

something?.doSomething()
something?.let{ ... }

Elvis-Operator ?:

val isValid = something?.isValid() ?: false
val isPropertyValid = something?.property?.isValid() ?: false

Data-Klassen

Java:

public class Note {
    private String title;
    private String content;
    private Date date;
    private Importance importance;
    
    public Note(String title) {
        this.title = title;
        this.date = new Date();
    }
    public Note(String title, String content) {
        this(title);
        this.content = content;
    }
    
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getDate() {
        return date;
    }
    public Importance getImportance() {
        return importance;
    }

    public void setImportance(Importance importance) {
        this.importance = importance;
    }

    public enum Importance {
        LOW, MEDIUM, HIGH
    }
}

Kotlin:

data class Note(var title: String, var content: String = "") {
    val date: Date = Date()
    var importance = Importance.HIGH
    
    init {
        ...
    }
    
    enum class Importance {
        LOW, MEDIUM, HIGH
    }
}
data class Note(var title: String, var content: String = "")
val note = Note("Technology Meetup", "2018")
val noteCopy = note.copy(title = "Technology Meetup")

String-Interpolation

Ohne ‘data’-Klasse:

val note = Note("Technology Meetup", "2018")
println("$note")
// Outputs: "Note: Note@3a71f4dd"

Mit ‘data’-Klasse:

val note = Note("Meetup", "2018")
println("$note")
// Outputs: "Note(title=Meetup, content=2018)"
println("Note title is: ${note.title}")
// Outputs: "Note title is: Meetup"

Attribut-Zugriff

Java:

private String mTitle;
public String getTitle() { ... }
public void setTitle(String title) { ... }

Kotlin:

note.title = "Technology Meetup"
println(note.title)

Kotlin:

var mTitle: String

Java:

note.setMTitle("Technology Meetup");
System.out.println(note.getMTitle());

Konstanten

Java:

class Constants {
    public static final String PLAYSTORE_PREFIX = "https://play.google.com/store/apps/details?id=";
}

Kotlin:

object Constants {
    const val PLAYSTORE_PREFIX = "https://play.google.com/store/apps/details?id=";
}
class MyClass {
    companion object {
        const val PLAYSTORE_PREFIX = "https://play.google.com/store/apps/details?id=";
    }
}

Calling Kotlin static fields from Java

String link = Constants.INSTANCE.getPLAYSTORE_PREFIX() + "my.package.name";
// Mit @JvmStatic
String link = Constants.getPLAYSTORE_PREFIX() + "my.package.name";
// Mit @JvmField
String link = Constants.PLAYSTORE_PREFIX + "my.package.name";

String link = MyClass.Companion.getPLAYSTORE_PREFIX() + "my.package.name";
// Mit @JvmStatic
String link = MyClass.getPLAYSTORE_PREFIX() + "my.package.name";
// Mit @JvmField
String link = MyClass.PLAYSTORE_PREFIX + "my.package.name";

Lambdas

Java:

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

Kotlin:

view.setOnClickListener {
    it.hide()
    doSomething()
}

SAM conversion = Single Abstract Method

Java:

interface ClickListener {
    void onClick(View view);
    void onLongClick(View view);
}

Kotlin:

object : ClickListener {
    override onClick(view: View){
        // TODO
    }
    
    override onLongClick(view: View){
        // TODO
    }
}

Java:

abstract class ClickListener {
    abstract void onClick(View view);
}

Kotlin:

object : ClickListener() {
    override onClick(view: View) {
        // TODO
    }
}

Höher geordnete Funktionen

fun something(newContent: String, extra: () -> Unit){
    content = newContent
    extra()
}

something("...") {
    // TODO
}
class Note(...) {
    ...
    fun something(newContent: String, extra: (Note) -> Unit) {
        content = newContent
        extra(this)
    }
    ...
}

val newContent = "..."
something(newContent) {
    it.title = "New title"
}
class Note(...) {
    ...
    fun something(extra:(Note, Boolean, Int) -> Unit) {
        extra(this, title.hasContent, 10)
    }
    ...
} 

something() { note, hasContent, _ ->
    note.title = "New title"
    note.content = if (hasContent) "Has content" else "It's empty"
}

Erweiterungs-Funktionen

fun Note.something(title: String, extra: (Note) -> Unit) {
    this.title = title 
    extra(this)
}

val note = Note(...)
val newTitle = "..."
note.something(newTitle) {
    it.importance = Note.Importance.LOW
}

Kontext übergeben

fun Note.something(title: String, extra: Note.() -> Unit) {
    this.title = title 
    extra(this)
}

val newTitle = "..."
something(newTitle) {
    importance = Note.Importance.LOW
}

Andere Beispiele

fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
        Toast.makeText(this, message, duration).show()

inline val String.hasContent
    get() = isNotBlank() && isNotEmpty()
    
fun View.showSnackbar(message: String, duration: Int = Snackbar.LENGTH_SHORT,
                      options: Snackbar.() -> Unit) {
    val snack = Snackbar.make(this, message, duration)
    snack.options()
    snack.show()
}

Aufruf mit Java

Default-Klassenname: MyClassKt.java

Mit Java aufrufen:

MyClassKt.showToast(activity, "This is the message");

Klassenname lässt sich in der ersten Zeile überschreiben:

@file:JvmName("MyUtils")

when

when (note.importance) {
    Note.Importance.LOW -> itemView.setBackgroundColor(Color.parseColor("#69F0AE"))
    Note.Importance.MEDIUM -> itemView.setBackgroundColor(Color.parseColor("#FFF176"))
    Note.Importance.HIGH -> itemView.setBackgroundColor(Color.parseColor("#FF8A80"))
}
when(hour) {
    0 -> print("Mitternacht")
    in 1..12 -> print("Morgens")
    20, 21 -> print("Entweder 20 oder 21 Uhr")
    is Float -> print("$hour ist Float")
    else -> print("Eine andere Stunde")
}

let

something?.let {
    it.doSomething()
    it.doAnotherThing()
}

apply

notesAdapter += Note(title, content).apply { importance = Note.Importance.HIGH }

anstatt:

val note = Note(title, content)
note.importance = Note.Importance.HIGH
notesAdapter += note

with

with(rv) {
    layoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.VERTICAL,
                                        false)
    addItemDecoration(
            DividerItemDecoration(this@MainActivity, DividerItemDecoration.VERTICAL))
    adapter = notesAdapter
}

entspricht:

rv.layoutManager = ...
rv.addItemDecoration(...)
rv.adapter = ...

Vererbung

class MyCustomView : View {
    ...
}

class MyOtherView : MyCustomView {
    // Nicht vererbbar, auch nicht in derselben Datei
    ...
}
sealed class MyCustomView : View {
    ...
}

class MyOtherView : MyCustomView {
    // Vererbbar, aber nur in derselben Datei
    ...
}
open class MyCustomView : View {
    ...
}

class MyOtherView : MyView {
    // Vererbbar
    ...
}

3. Zusätzliche Android-spezifische Funktionen und Tools

Views

class MyCustomView : View {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
    constructor(ctx: Context, attrs: AttributeSet, style: Int) : super(ctx, attrs, style)
    ...
}

Kotlin Android Extensions

...
    <TextView
            android:id="@+id/date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textStyle="bold"/>
...
import kotlinx.android.synthetic.main.item_note.view.date

fun bind(note: Note, listener: (Note, Boolean) -> Unit) {
    ...
    date.text = note.date.toString()
    ...
}
apply plugin: 'kotlin-android-extensions'

Anko

Anko commons:

  • Intents
  • Dialoge
  • Logging
  • Ressourcen

Anko layouts: Layouts mit Hilfe von DSL deklarieren

Und mehr!

verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}
dependencies {
    compile "org.jetbrains.anko:anko:$anko_version"
}

Android KTX

  • Bibliothek von Google
  • Vereinfacht Nutzung von einigen Funktionen

Kotlin:

sharedPreferences.edit()
    .putBoolean("key", value)
    .apply()

Kotlin mit Android KTX:

sharedPreferences.edit {
    putBoolean("key", value)
}
repositories {
    google()
}

dependencies {
    implementation 'androidx.core:core-ktx:0.3'
}

4. Argumente für Kotlin

  • Interoperable
  • Kein Null-Pointer-Exceptions
  • Einfach zu lesen
  • Relativ einfach zu lernen
  • Kompiliert schneller
  • Weniger Abstürze
  • Sauberer Code
  • WIN-WIN

Firmen, die Kotlin einsetzen

  • Pinterest
  • Evernote
  • Uber
  • Atlassian
  • Flipboard
  • Square
  • Und viele mehr…

Kotlin Dokumentation - kotlinlang.org/docs/reference AndroidPub - androidpub.net

Kotlin ausprobieren

try.kotlinlang.org

Das war’s!

Folien: jlelse.dev/slides

Web: jlelse.dev
GitHub: jlel.se/gh
LinkedIn: jlel.se/linked