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

AndroidDev Powered by Kotlin

AndroidDev Powered by Kotlin

A talk about the language features that android developers can take advantage of.

Presented at droidcon Dubai 2018 (droidcon.ae)
Video available: https://www.youtube.com/watch?v=ofysYx1TEpA

9ab0b3b080e75e0c03a0c643333f8b93?s=128

Segun Famisa

April 14, 2018
Tweet

Transcript

  1. #AndroidDev Powered by Kotlin! making the most of Kotlin for

    Android dev
  2. Android Engineer at trivago oluwasegun.famisa@trivago.com Oluwasegun Famisa

  3. 190 countries 55 global regions 1.8m+ hotels Founded in 2005

    Hotel search and price comparison Available on Android, iOS, Web, PWA
  4. About Kotlin

  5. About Kotlin

  6. - Functional vs Imperative programming

  7. Imperative == structured in steps of execution and usually reads

    as “how it’s done”
  8. Functional == actions are composed into functions and usually reads

    as “what is done” Imperative == structured in steps of execution and usually reads as “how it’s done”
  9. // Java - print even numbers for (int i =

    start; i < end; i++) { if (i % 2 == 0) { System.out.println(i); } }
  10. // Java - print even numbers for (int i =

    start; i < end; i++) { if (i % 2 == 0) { System.out.println(i); } } // Kotlin - print even numbers (start until end) .filter { it % 2 == 0 } .map { println(it) }
  11. Imperative Functional Immutability isn’t encouraged at language level Encourages immutability

    Instances of classes, structures, objects are first class citizens Functions are first class citizens Loops, method calls, conditionals Function calls Side-effects allowed in functions Pure functions
  12. Some interesting features

  13. - nullable types

  14. Kotlin introduces nullability. At the time of instantiating an object,

    you tell the compiler whether the object can be null or not
  15. val name: String = null // error (non-nullable type) val

    email: String? = null // works fine (nullable type)
  16. val name: String = “Segun” val email: String? = null

    name.length // works fine email.length // compiler error (unsafe access) email?.length // works fine (safe access)
  17. Free from NullPointerException!

  18. Free from NullPointerException! Except you... 1. throw NullPointerException 2. use

    the “!!” 3. call Java code that causes it
  19. - immutability

  20. “Classes should be immutable unless there's a very good reason

    to make them mutable.” - Joshua Bloch (Effective Java)
  21. Immutable objects == thread-safe

  22. // Immutable class in Java public final class User {

    // private fields private final int id; private final int email; // public constructor public User(int id, String email){...} // Getters public int getId() {...} public String getEmail() {...} // No public setters ... }
  23. // Immutable class in Java public final class User {

    // private fields private final int id; private final int email; // public constructor public User(int id, String email){...} // Getters public int getId() {...} public String getEmail() {...} // No public setters ... } // Immutable class in Kotlin class User(val id: Int, val email: String)
  24. // Immutable class in Kotlin class User(val id: Int, val

    email: String) Classes are final by default in Kotlin. To make a class non-final, you have to use the “open”
  25. // Immutable class in Kotlin class User(val id: Int, val

    email: String) Classes are final by default in Kotlin. To make a class non-final, you have to use the “open” open class User(val id: Int, val email: String)
  26. // Immutable class in Kotlin class User(val id: Int, val

    email: String) val vs var
  27. // Immutable class in Kotlin class User(val id: Int, val

    email: String) val vs var val is like final in Java. Once created, cannot be re-assigned value var can be re-assigned another value
  28. Quick tip: Reading generated Java code helps with better understanding

  29. 1

  30. 2

  31. 3

  32. - data classes

  33. // Data holding class in Java public final class User

    { ... public User(int id, String email){...} // Getters public final int getId() {...} public final String getEmail() {...} }
  34. // Data holding class in Java public final class User

    { ... public User(int id, String email){...} // Getters public final int getId() {...} public final String getEmail() {...} } // Kotlin data class User( val id: Int, val email: String )
  35. data class User( val id: Int, val email: String )

    // Generated Java code public final class User { ... public User(int id, String email){...} // Getters public final int getId() {...} public final String getEmail() {...} public String toString() {...} public int hashCode() {...} public boolean equals(Object o) {...} ... }
  36. // Generated Java code public final class User { ...

    ... public final int component1() {...} public final String component2() {...} public final User copy(...) {...} } data class User( val id: Int, val email: String )
  37. Kotlin data classes allow us do something called “destructuring declaration”

  38. // Person data class data class Person(val name: String, val

    age: Int)
  39. // Person data class data class Person(val name: String, val

    age: Int) // Destructure val (name, age) = person
  40. // Person data class data class Person(val name: String, val

    age: Int) // Destructure val (name, age) = person But how?
  41. // Person data class data class Person(val name: String, val

    age: Int) // Generated functions public final String component1() {...} // name
  42. // Person data class data class Person(val name: String, val

    age: Int) // Generated functions public final String component1() {...} // name public final int component2() {...} // age
  43. // Person data class data class Person(val name: String, val

    age: Int..) // Generated functions public final String component1() {...} // name public final int component2() {...} // age ... public final Type componentN() {...} //nth data class arg
  44. // Person data class data class Person(val name: String, val

    age: Int) // Destructure val (name, age) = person // Same as val name = person.component1() val age = person.component2()
  45. // Person data class data class Person(val name: String, val

    age: Int) val people = getPeople() // Use destructuring in for loops for ((name, age) in people) {...}
  46. // Person data class data class Person(val name: String, val

    age: Int) val people = getPeople() // Use destructuring in for loops for ((name, age) in people) {...}
  47. Some potential use cases for data class in Android: •

    Retrofit request & response classes • Domain models • Entities
  48. - sealed classes

  49. • Sealed classes are like enums but even better. •

    We can use them to solve many everyday Android situations.
  50. sealed class Result { class Success(val value: Any) : Result()

    class Error(val errorMessage: String) : Result() }
  51. // Usage: when (result) { is Result.Success -> // do

    something with result.value is Result.Error -> // do something with result.errorMessage } sealed class Result { class Success(val value: Any) : Result() class Error(val errorMessage: String) : Result() }
  52. // Usage: when (result) { is Result.Success -> // do

    something with result.value is Result.Error -> // do something with result.errorMessage } But how does it work? sealed class Result { class Success(val value: Any) : Result() class Error(val errorMessage: String) : Result() }
  53. // Java public abstract class Result { public static final

    class Success extends Result {...} public static final class Error extends Result {...} } // Kotlin sealed class Result { class Success(val value: Any) : Result() class Error(val errorMessage: String) : Result() }
  54. - functions are fun!

  55. Functions are first class citizens!!!

  56. Functions are first class citizens!!! They can: 1. be stored

    in a variable - just like other types 2. take another function as parameter 3. return a function
  57. Functions are first class citizens!!! They can: 1. be stored

    in a variable - just like other types 2. take another function as parameter 3. return a function a.k.a “Higher order function”
  58. Higher order function fun atLeastAndroidO(action: () -> Unit) { if

    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } }
  59. Higher order function fun atLeastAndroidO(action: () -> Unit) { if

    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } }
  60. Higher order function fun atLeastAndroidO(action: () -> Unit) { if

    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } } // Usage: atLeastAndroidO { // do something requiring Android O }
  61. // Kotlin higher order function fun atLeastAndroidO(action: () -> Unit)

    { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } } // Generated Java code public final void atLeastAndroidO(Function0 action) { if (VERSION.SDK_INT >= 26) { action.invoke(); } }
  62. // Kotlin higher order function fun atLeastAndroidO(action: () -> Unit)

    { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } } // Generated Java code public final void atLeastAndroidO(Function0 action) { if (VERSION.SDK_INT >= 26) { action.invoke(); } } Every lambda is an object. Not good for performance
  63. - inline functions

  64. // Kotlin higher order function inline fun atLeastAndroidO(action: () ->

    Unit) {...} // Use like this: fun doSomethingOnOreo() { atLeastAndroidO { println("Hey, running on O") } }
  65. // Kotlin higher order function inline fun atLeastAndroidO(action: () ->

    Unit) {...} // Use like this fun doSomethingOnOreo() { atLeastAndroidO { println("Hey, running on O") } } // Generated code // Unused generated method void atLeastAndroidO(Function0 action) {...} void doSomethingOnOreo() { if (VERSION.SDK_INT >= 26) { System.out.println("Hey, running on O"); } }
  66. // Kotlin higher order function inline fun atLeastAndroidO(action: () ->

    Unit) {...} // Use like this fun doSomethingOnOreo() { atLeastAndroidO { println("Hey, running on O") } } method body is copied to the call-site // Generated code // Unused generated method void atLeastAndroidO(Function0 action) {...} void doSomethingOnOreo() { if (VERSION.SDK_INT >= 26) { System.out.println("Hey, running on O"); } }
  67. - extension functions

  68. • Extend existing classes, even those we don’t own. Extension

    functions allow us:
  69. • Extend existing classes, even those we don’t own. •

    Improve code readability Extension functions allow us:
  70. • Extend existing classes, even those we don’t own. •

    Improve code readability • Get rid of Utils.java or Utils.kt classes Extension functions allow us:
  71. • Extend existing classes, even those we don’t own. •

    Improve code readability • Get rid of Utils.java or Utils.kt classes Extension functions allow us: But how?
  72. Extension functions are resolved statically. // Kotlin ext fun fun

    Int.isEven(): Boolean { return this and 1 == 0 }
  73. Extension functions are resolved statically. // Kotlin ext fun fun

    Int.isEven(): Boolean { return this and 1 == 0 } // Java generated code public static final boolean isEven(int $receiver) { return ($receiver & 1) == 0; }
  74. Extension functions are resolved statically. // Kotlin ext fun fun

    Int.isEven(): Boolean { return this and 1 == 0 } // Java generated code public static final boolean isEven(int $receiver) { return ($receiver & 1) == 0; } // Usage 1.isEven() // evaluates to false
  75. • Set of Kotlin Extensions for Android development Android KTX

  76. Android KTX // Without Android KTX val fileUri = Uri.fromFile(...)

  77. Android KTX // Without Android KTX val fileUri = Uri.fromFile(...)

    // With Android KTX val fileUri = file.toUri()
  78. Android KTX // Without Android KTX val fileUri = Uri.fromFile(...)

    // With Android KTX val fileUri = file.toUri() // Under the hood inline fun File.toUri(): Uri = Uri.fromFile(this)
  79. // Iterating through a ViewGroup's children for (i in 0

    until viewGroup.childCount) { val view = viewGroup.getChildAt(i) // do something with view }
  80. operator fun ViewGroup.iterator() = object : MutableIterator<View> { private var

    index = 0 override fun hasNext() = index < childCount override fun next() = getChildAt(index++) override fun remove() = removeViewAt(--index) }
  81. operator fun ViewGroup.iterator() = object : MutableIterator<View> { private var

    index = 0 override fun hasNext() = index < childCount override fun next() = getChildAt(index++) override fun remove() = removeViewAt(--index) } Extension function on ViewGroup class!
  82. operator fun ViewGroup.iterator() ... // Now we can do this

    for (view in viewGroup) { // do something with view }
  83. Android KTX There’s more! Check out: https://github.com/android/android-ktx

  84. - operator overloading

  85. • Custom behaviour for standard operators Operator overloading allows us

    to:
  86. • Custom behaviour for standard operators • Implement for our

    own classes as well as other classes we don’t own. Operator overloading allows us to:
  87. • Custom behaviour for standard operators • Implement for our

    own classes as well as other classes we don’t own. Operator overloading allows us to: Implemented by using the operator keyword //Example operator fun ViewGroup.iterator() ...
  88. // Product data class Product(val price: Double) TODO("Easily determine whether

    the price of a product is higher than another")
  89. // Product data class Product(val price: Double) TODO("Easily determine whether

    the price of a product is higher than another") There’s an a̶p̶p̶ operator for that
  90. // Product data class Product(val price: Double) { operator fun

    compareTo(other: Product): Int { return this.price.compareTo(other.price) } }
  91. // Product data class Product(val price: Double) { operator fun

    compareTo(other: Product): Int { return this.price.compareTo(other.price) } } > < >= <=
  92. // Product data class Product(val price: Double) { operator fun

    compareTo(other: Product): Int { return this.price.compareTo(other.price) } } // Usage val productA = Product(price = 200.00) val productB = Product(price = 800.00) productA > productB // evaluates to false productA < productB // evaluates to true
  93. Let’s see another example of operator overloading

  94. data class ShoppingCart(val items: MutableList<Product>)

  95. data class ShoppingCart(val items: MutableList<Product>) TODO("Ability to add a new

    product to the shopping cart")
  96. data class ShoppingCart(val items: MutableList<Product>) TODO("Ability to add a new

    product to the shopping cart") val cart = createEmptyCart() // one way to go cart.items.add(product)
  97. data class ShoppingCart(val items: MutableList<Product>) TODO("Ability to add a new

    product to the shopping cart") val cart = createEmptyCart() // one way to go cart.items.add(product)
  98. data class ShoppingCart(val items: MutableList<Product>) { operator fun plusAssign(product: Product)

    { items.add(product) } }
  99. data class ShoppingCart(val items: MutableList<Product>) { operator fun plusAssign(product: Product)

    { items.add(product) } } // Usage of the plusAssign operator function val cart = createEmptyCart() cart += Product(price = 12.00)
  100. // Usage of the plusAssign operator function val cart =

    createEmptyCart() cart += Product(price = 12.00) data class ShoppingCart(val items: MutableList<Product>) { operator fun plusAssign(product: Product) { items.add(product) } }
  101. ... operator fun inc() ...// a++ operator fun dec() ...//

    a-- operator fun plus(other: T) ...// a + b operator fun minus(other: T) ...// a - b operator fun get(index: Int) ...// a[i] operator fun contains(item: T) ...// a in b ... More examples:
  102. - interoperability

  103. - platform type Calling Java code from Kotlin

  104. String! String? Pop What are thossseeee nullability types? String

  105. String String! String? Non-nullable type Nullable type Platform type Kotlin

    compiler has no idea whether it’s nullable or not
  106. Types are very important when considering interoperability between Java &

    Kotlin
  107. // Java @NotNull public String getName() { return "Segun"; }

    ... // Calling Java from Kotlin // Compiles fine val nameLength = myClass.name.length
  108. // Java @Nullable public String getName() { return “Segun”; }

    ... // Calling Java from Kotlin // Compiler error val nameLength = myClass.name.length
  109. // Java @Nullable public String getName() { return “Segun”; }

    ... // Calling Java from Kotlin // Compiles fine val nameLength = myClass.name?.length
  110. // Java public String getName() { return null; } ...

    // Calling Java from Kotlin // Compiles fine. Throws NullPointer in runtime val nameLength = myClass.name.length
  111. // Java public String getName() { return null; } ...

    // Calling Java from Kotlin // Compiles fine. Evaluates to null val nameLength = myClass.name?.length
  112. ... @NotNull public String getName() { return "Segun"; } ...

    @Nullable public String getName() { return "Segun"; } ... public String getName() { return "Segun"; } ... String String? String!
  113. Example of this in Android framework code! // Support library

    < 27 class Fragment { ... final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } }
  114. Example of this in Android framework code! // Support library

    < 27 class Fragment { ... final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } } // compiles ok but could throw an exception in runtime! activity.supportActionBar = ...
  115. Example of this in Android framework code! // Support library

    >= 27 class Fragment { ... @Nullable final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } }
  116. Example of this in Android framework code! // Support library

    >= 27 class Fragment { ... @Nullable final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } } // compiler error! Should use safe access activity.supportActionBar = ...
  117. Example of this in Android framework code! // Support library

    >= 27 class Fragment { ... @Nullable final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } } // compiles ok, safe in runtime too activity?.supportActionBar = ...
  118. @JvmOverloads

  119. @JvmOverloads Generates overloaded methods and constructors for class signatures and

    methods that have default values
  120. @JvmOverloads Generates overloaded methods and constructors for class signatures and

    methods that have default values Example??
  121. // Kotlin class without @JvmOverloads class Point(val label: String, val

    x: Double = 0.0, val y: Double = 0.0)
  122. // Kotlin class without @JvmOverloads class Point(val label: String, val

    x: Double = 0.0, val y: Double = 0.0) // Generated constructor public Point(@NotNull String label, double x, double y)
  123. // Kotlin class without @JvmOverloads class Point(val label: String, val

    x: Double = 0.0, val y: Double = 0.0) // Kotlin class with @JvmOverloads class Point @JvmOverloads constructor(val label: String, val x: Double = 0.0, val y: Double = 0.0)
  124. // Kotlin class without @JvmOverloads class Point(val label: String, val

    x: Double = 0.0, val y: Double = 0.0) // Generated constructors public Point(@NotNull String label) public Point(@NotNull String label, double x) public Point(@NotNull String label, double x, double y) // Kotlin class with @JvmOverloads class Point @JvmOverloads constructor(val label: String, val x: Double = 0.0, val y: Double = 0.0)
  125. @JvmField

  126. @JvmField Helps to expose a Kotlin class property as a

    Java field. Example??
  127. class Data(val value: Int...) : Parcelable { private constructor(parcel: Parcel)...

    ... companion object { val CREATOR: Parcelable.Creator<Data> = ... } }
  128. class Data(val value: Int...) : Parcelable { private constructor(parcel: Parcel)...

    ... companion object { val CREATOR: Parcelable.Creator<Data> = ... } } Compiler error. Documentation says: Classes implementing the Parcelable interface must also have a non-null static field called CREATOR of a type that implements the Parcelable.Creator interface.
  129. class Data(val value: Int...) : Parcelable { private constructor(parcel: Parcel)...

    ... companion object { @JvmField val CREATOR: Parcelable.Creator<Data> = ... } } CREATOR is exposed as a field, and everything is good!
  130. @JvmStatic @JvmName @JvmMultifileClass... etc There’s more:

  131. Code reviews in Kotlin

  132. Look out for imperative styles

  133. // Imperative - Java-ish style fun sendMessageToClient(client: Client?,message: String?,mailer: Mailer)

    { if (client == null || message == null) return val personalInfo = client.personalInfo ?: return val email = personalInfo.email ?: return mailer.sendMessage(email, message) }
  134. // Imperative - Java-ish style fun sendMessageToClient(client: Client?,message: String?,mailer: Mailer)

    { if (client == null || message == null) return val personalInfo = client.personalInfo ?: return val email = personalInfo.email ?: return mailer.sendMessage(email, message) } // Suggest to do this instead - more functional than imperative fun sendMessageToClient(client: Client?, message: String?, mailer: Mailer) { if (client != null && message != null) { client.personalInfo ?.email ?.run { mailer.sendMessage(this, message) } } }
  135. // Don’t do this! val person = Person() person.age =

    25 person.name = "Oluwasegun" person.email = "oluwasegun.famisa@trivago.com" person.cars = emptyList()
  136. // Don’t do this! val person = Person() person.age =

    25 person.name = "Oluwasegun" person.email = "oluwasegun.famisa@trivago.com" person.cars = emptyList() // suggest to do this instead - it reads better val person = Person().apply { age = 25 name = "Oluwasegun" email = "oluwasegun.famisa@trivago.com" cars = emptyList() }
  137. Overuse of scoping functions

  138. Be nice and courteous.

  139. Road to 100% Kotlin

  140. • Add Kotlin support to your project

  141. • Add Kotlin support to your project • Migrate API

    responses to use Kotlin data classes
  142. • Add Kotlin support to your project • Migrate API

    responses to use Kotlin data classes • Convert interfaces from Java to Kotlin
  143. • Add Kotlin support to your project • Migrate API

    responses to use Kotlin data classes • Convert interfaces from Java to Kotlin • Add tests in Kotlin
  144. • Add Kotlin support to your project • Migrate API

    responses to use Kotlin data classes • Convert interfaces from Java to Kotlin • Add tests in Kotlin • Gradually migrate the rest of the code base
  145. • Add Kotlin support to your project • Migrate API

    responses to use Kotlin data classes • Convert interfaces from Java to Kotlin • Add tests in Kotlin • Gradually migrate the rest of the code base • Profit
  146. Staying up to date with Kotlin

  147. Staying up to date with Kotlin... kotlin.link kotlinweekly.net https://github.com/jetbrains/kotlin/pulls https://youtrack.jetbrains.com/issues/KT

    https://github.com/Kotlin/KEEP androidweekly.net KotlinConf videos #Kotlin on twitter
  148. Come join me and my team mates! trv.to/droidcondubai