Slide 1

Slide 1 text

Kotlin tips & tricks Lorenzo Quiroli

Slide 2

Slide 2 text

About me: Lorenzo Quiroli • Android Engineer • Works @ busuu, London • Kotlin lover

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Learning a new programming language makes you a better engineer

Slide 5

Slide 5 text

Few things to know about Kotlin

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

3: Sealed class are perfect to represent your abstraction sealed class Request { data class Result(val data: T): Request() data class Error(code: Int): Request() }

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

4: Interoperability, calling Kotlin from Java What about top level functions? // Counter.kt fun count() = 42 // Java class int size = CounterKt.count() !

Slide 13

Slide 13 text

4: Interoperability, calling Kotlin from Java We can fix it! // Counter.kt @file:JvmName("Counter") fun count() = 42 // Java class int size = Counter.count()

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

4: Interoperability, calling Kotlin from Java @JvmField to the rescue // Kotlin @JvmField val count = 42 // Java class int size = something.count

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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!

Slide 20

Slide 20 text

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"

Slide 21

Slide 21 text

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!") }

Slide 22

Slide 22 text

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!") }

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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.

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

5: Interoperability, calling Java from Kotlin You can use a lot of different annotation libraries: • JetBrains • Android • JSR-305 • Many others!

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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!

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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!"

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

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?)

Slide 35

Slide 35 text

7: Handling nullability Null checking is simple... val nullableString = getString() if (nullableString != null) { nullableString.length // safe call! } else { // do something else }

Slide 36

Slide 36 text

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 }

Slide 37

Slide 37 text

7: Handling nullability Solution 1: use let if you don't need an else case var nullableString = getString() nullableString?.let { it.length }

Slide 38

Slide 38 text

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 }

Slide 39

Slide 39 text

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)

Slide 40

Slide 40 text

7: Handling nullability Sometimes... val context: Context? = fragment.context

Slide 41

Slide 41 text

7: Handling nullability Sometimes... val context: Context? = fragment.context // since Support Library 27.1.0 val context: Context = fragment.requireContext()

Slide 42

Slide 42 text

7: Handling nullability @NonNull public final Context requireContext() { Context context = getContext(); if (context == null) { throw new IllegalStateException("..."); } return context; }

Slide 43

Slide 43 text

8: What about my constants What's the best place for them? Two alternatives: • Companion object • Top level

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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" }

Slide 47

Slide 47 text

8: What about my constants Top level constants // file private, not class private! private const val A_VALUE = "constant"

Slide 48

Slide 48 text

8: What about my constants What if it's not primitives / String? // Singleton private object Admin: Person("admin")

Slide 49

Slide 49 text

9: Build time Is it going to increase?

Slide 50

Slide 50 text

9: Build time Is it going to increase? Yes

Slide 51

Slide 51 text

9: Build time Is it going to increase? Yes But you're still saving time

Slide 52

Slide 52 text

9: Build time Is it going to increase? Yes But you're still saving time And it will get better

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

We're hiring! • See https://www.busuu.com/en/jobs/ • Write to me [email protected]

Slide 56

Slide 56 text

Thanks!