Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin tips and tricks

Kotlin tips and tricks

Droidcon Italy 2018 presentation about Kotlin Tips and Tricks

Lorenzo Quiroli

April 20, 2018
Tweet

More Decks by Lorenzo Quiroli

Other Decks in Technology

Transcript

  1. The road from Java to Kotlin • They are fairly

    similar languages • Kotlin doesn't try to reinvent the wheel • Fix the annoying part of Java, keep the rest
  2. 1: Prefere val to var • val means read-only (final

    in Java) • No guarantee about immutability • Immutability -> thread safety • Even you are using var, try to think about why • Try to get rid / isolate state
  3. 2: Use inheritance only when it's needed • In Java

    everything is open by default, unless you add the keyword final • In Kotlin everything is closed by default, unless you add the keyword open • Prefer composition when it's possible • A data class cannot be open • Inheritance breaks equality
  4. 2: Use inheritance only when it's needed open data class

    Base(val name: String) data class Derived(name: String, val secondName: String): Base(name) val base = Base("base") val derived = Derived("base", "other") base == derived // true derived == base // false
  5. 3: Sealed class are perfect to represent your abstraction •

    Can represent a closed domain • Can use both object and class
  6. 3: Sealed class are perfect to represent your abstraction sealed

    class Request<out T> { data class Result<out T>(val data: T): Request<T>() data class Error(code: Int): Request<Nothing>() }
  7. 4: Interoperability, calling Kotlin from Java Properties generate their own

    getter and setter (only if it's a var) // Kotlin class val count: Int // Java class int size = something.getCount()
  8. 4: Interoperability, calling Kotlin from Java What about top level

    functions? // Counter.kt fun count() = 42 // Java class int size = CounterKt.count() !
  9. 4: Interoperability, calling Kotlin from Java We can fix it!

    // Counter.kt @file:JvmName("Counter") fun count() = 42 // Java class int size = Counter.count()
  10. 4: Interoperability, calling Kotlin from Java What if you don't

    want getter and setter to be generated? // Kotlin val count = 42 // Java class int size = something.getCount()
  11. 4: Interoperability, calling Kotlin from Java @JvmField to the rescue

    // Kotlin @JvmField val count = 42 // Java class int size = something.count
  12. 4: Interoperability, calling Kotlin from Java What if I want

    a static method in a companion object? // Kotlin companion object { fun count() = 42 } // Java class int size = Counter.Companion.count()
  13. 4: Interoperability, calling Kotlin from Java Use @JvmStatic // Kotlin

    companion object { @JvmStatic fun count() = 42 } // Java class int size = Counter.count()
  14. 4: Interoperability, calling Kotlin from Java I have default parameter

    in my method / constructor // Kotlin class Person(val fullName: String, val nickName: String? = null) // Java class Person person = new Person("Lorenzo"); // error
  15. 4: Interoperability, calling Kotlin from Java Generate overloads with @JvmOverloads

    // Kotlin class Person @JvmOverloads constructor( val fullName: String, val nickName: String? = null ) // Java class Person person = new Person("Lorenzo"); // OK!
  16. 5: Interoperability, calling Java from Kotlin Kotlin tries to behave

    with Java code as it does with Kotlin code // accessing through person.getName() val name = person.name // accessing through person.setName(String newName) person.name = "Mario"
  17. 5: Interoperability, calling Java from Kotlin SAM conversion allows you

    to create implementations of Java interfaces with a single non-default method public interface Action { void run(); } val action = Action { println("Yay!") }
  18. 5: Interoperability, calling Java from Kotlin It doesn't work with

    Kotlin interfaces! interface Action { fun run() } // error: interface Action does not have a constructor! val action = Action { println("Yay!") }
  19. 5: Interoperability, calling Java from Kotlin Use proper function types

    in Kotlin! val action: () -> Unit = { println("Yay!") }
  20. 5: Interoperability, calling Java from Kotlin Use proper function types

    in Kotlin! typealias Action = () -> Unit val action: Action = { println("Yay!") }
  21. 5: Interoperability, calling Java from Kotlin Use nullability annotation in

    your Java code public String getString() The return type when called from Kotlin will be String!, as the compiler will not have any clue about it.
  22. 5: Interoperability, calling Java from Kotlin public String getString() val

    nullableString: String? = something.getString() val notNullString: String = something.getString() Compiler is gonna be like ¯\_(ϑ)_/¯
  23. 5: Interoperability, calling Java from Kotlin @Nullable public String getString()

    // This is fine val nullableString: String? = something.getString() // Type mismatch: required String found String? val notnullString: String = something.getString() The compiler is now able to help us
  24. 5: Interoperability, calling Java from Kotlin You can use a

    lot of different annotation libraries: • JetBrains • Android • JSR-305 • Many others!
  25. 6: Extension functions Adding behaviour to your class has never

    been easier! fun Activity.toast(text: String) = Toast.make(this, text, Toast.LENGTH_SHORT) .show()
  26. 6: Extension functions Few things to know: • They can

    be declared both as top level functions or inside a class • They don't really modify the receiver class • They are compiled as static functions only if they are top level and so they are also dispatched statically!
  27. 6: Extension functions open class Base class Derived : Base()

    fun Base.shout() = println("I'm base!") fun Derived.shout() = println("I'm derived!") val first = Derived() val second: Base = Derived() first.shout() second.shout()
  28. 6: Extension functions open class Base class Derived : Base()

    fun Base.shout() = println("I'm base!") fun Derived.shout() = println("I'm derived!") val first = Derived() val second: Base = Derived() first.shout() // "I'm derived!" second.shout() // "I'm base!"
  29. 6: Extension functions An extension function can be called from

    Java as a static method @file:JvmName("StringUtils") fun String.hasFiveChar() = length == 5 // Kotlin val hasFiveChar = string.hasFiveChar() // Java boolean hasFiveChar = StringUtils.hasFiveChar(string)
  30. 7: Handling nullability • Think about your model and try

    to understand what's optional and what's not • When something is optional, try to understand what's going to happen when it's absent (is there a default? Or just don't show that piece of information?)
  31. 7: Handling nullability Null checking is simple... val nullableString =

    getString() if (nullableString != null) { nullableString.length // safe call! } else { // do something else }
  32. 7: Handling nullability Null checking is simple... But it doesn't

    work with var var nullableString = getString() if (nullableString != null) { nullableString.length // error! } else { // do something else }
  33. 7: Handling nullability Solution 1: use let if you don't

    need an else case var nullableString = getString() nullableString?.let { it.length }
  34. 7: Handling nullability Solution 2: create a local copy var

    nullableString = getString() val copiedString = nullableString if (copiedString != null) { copiedString.length // safe call! } else { // do something else }
  35. 7: Handling nullability Solution 3: create a function fun doSomethingWith(data

    : String?) { if (data != null) { data.length // safe call! } else { // do something else } } var nullableString = getString() doSomethingWith(nullableString)
  36. 7: Handling nullability Sometimes... val context: Context? = fragment.context //

    since Support Library 27.1.0 val context: Context = fragment.requireContext()
  37. 7: Handling nullability @NonNull public final Context requireContext() { Context

    context = getContext(); if (context == null) { throw new IllegalStateException("..."); } return context; }
  38. 8: What about my constants What's the best place for

    them? Two alternatives: • Companion object • Top level
  39. 8: What about my constants Companion object inside a class

    companion object { // it will be accessed through a getter val A_VALUE = "constant" }
  40. 8: What about my constants Companion object inside a class

    companion object { @JvmField // Accessed directly val A_VALUE = "constant" }
  41. 8: What about my constants Companion object inside a class

    Even better companion object { // const works for primitive types and string // inlined by the compiler! const val A_VALUE = "constant" }
  42. 8: What about my constants Top level constants // file

    private, not class private! private const val A_VALUE = "constant"
  43. 8: What about my constants What if it's not primitives

    / String? // Singleton private object Admin: Person("admin")
  44. 9: Build time Is it going to increase? Yes But

    you're still saving time And it will get better
  45. 9: Build time Things to know: • Support for Gradle

    build cache since Kotlin 1.2.20 (add org.gradle.caching=true in your gradle.properties) • Avoid recompilation of Kotlin file adding kotlin.incremental.usePreciseJavaTracking=true to your gradle.properties (experimental!) • Gradle 4.7 supports incremental annotation
  46. Bonus: useful links • 31 Days of Kotlin: https://goo.gl/GhwkRN •

    Egor Adreevici's blog post about constants in Kotlin: https://goo.gl/7ixeBf • Kotlin official docs: https://goo.gl/RxwPrs