Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

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

Segun Famisa

April 14, 2018
Tweet

More Decks by Segun Famisa

Other Decks in Programming

Transcript

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

    Hotel search and price comparison Available on Android, iOS, Web, PWA
  2. 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”
  3. // Java - print even numbers for (int i =

    start; i < end; i++) { if (i % 2 == 0) { System.out.println(i); } }
  4. // 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) }
  5. 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
  6. Kotlin introduces nullability. At the time of instantiating an object,

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

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

    name.length // works fine email.length // compiler error (unsafe access) email?.length // works fine (safe access)
  9. “Classes should be immutable unless there's a very good reason

    to make them mutable.” - Joshua Bloch (Effective Java)
  10. // 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 ... }
  11. // 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)
  12. // 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”
  13. // 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)
  14. // 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
  15. 1

  16. 2

  17. 3

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

    { ... public User(int id, String email){...} // Getters public final int getId() {...} public final String getEmail() {...} }
  19. // 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 )
  20. 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) {...} ... }
  21. // 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 )
  22. // Person data class data class Person(val name: String, val

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

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

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

    age: Int) // Generated functions public final String component1() {...} // name public final int component2() {...} // age
  26. // 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
  27. // 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()
  28. // 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) {...}
  29. // 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) {...}
  30. Some potential use cases for data class in Android: •

    Retrofit request & response classes • Domain models • Entities
  31. • Sealed classes are like enums but even better. •

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

    class Error(val errorMessage: String) : Result() }
  33. // 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() }
  34. // 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() }
  35. // 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() }
  36. 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
  37. 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”
  38. Higher order function fun atLeastAndroidO(action: () -> Unit) { if

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

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

    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } } // Usage: atLeastAndroidO { // do something requiring Android O }
  41. // 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(); } }
  42. // 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
  43. // Kotlin higher order function inline fun atLeastAndroidO(action: () ->

    Unit) {...} // Use like this: fun doSomethingOnOreo() { atLeastAndroidO { println("Hey, running on O") } }
  44. // 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"); } }
  45. // 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"); } }
  46. • Extend existing classes, even those we don’t own. •

    Improve code readability Extension functions allow us:
  47. • 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:
  48. • 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?
  49. Extension functions are resolved statically. // Kotlin ext fun fun

    Int.isEven(): Boolean { return this and 1 == 0 }
  50. 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; }
  51. 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
  52. Android KTX // Without Android KTX val fileUri = Uri.fromFile(...)

    // With Android KTX val fileUri = file.toUri()
  53. 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)
  54. // Iterating through a ViewGroup's children for (i in 0

    until viewGroup.childCount) { val view = viewGroup.getChildAt(i) // do something with view }
  55. 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) }
  56. 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!
  57. operator fun ViewGroup.iterator() ... // Now we can do this

    for (view in viewGroup) { // do something with view }
  58. • Custom behaviour for standard operators • Implement for our

    own classes as well as other classes we don’t own. Operator overloading allows us to:
  59. • 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() ...
  60. // 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
  61. // Product data class Product(val price: Double) { operator fun

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

    compareTo(other: Product): Int { return this.price.compareTo(other.price) } } > < >= <=
  63. // 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
  64. 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)
  65. 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)
  66. 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)
  67. // 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) } }
  68. ... 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:
  69. String String! String? Non-nullable type Nullable type Platform type Kotlin

    compiler has no idea whether it’s nullable or not
  70. // Java @NotNull public String getName() { return "Segun"; }

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

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

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

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

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

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

    < 27 class Fragment { ... final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } }
  77. 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 = ...
  78. Example of this in Android framework code! // Support library

    >= 27 class Fragment { ... @Nullable final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } }
  79. 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 = ...
  80. 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 = ...
  81. // 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)
  82. // 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)
  83. // 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)
  84. class Data(val value: Int...) : Parcelable { private constructor(parcel: Parcel)...

    ... companion object { val CREATOR: Parcelable.Creator<Data> = ... } }
  85. 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.
  86. 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!
  87. // 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) }
  88. // 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) } } }
  89. // Don’t do this! val person = Person() person.age =

    25 person.name = "Oluwasegun" person.email = "[email protected]" person.cars = emptyList()
  90. // Don’t do this! val person = Person() person.age =

    25 person.name = "Oluwasegun" person.email = "[email protected]" person.cars = emptyList() // suggest to do this instead - it reads better val person = Person().apply { age = 25 name = "Oluwasegun" email = "[email protected]" cars = emptyList() }
  91. • Add Kotlin support to your project • Migrate API

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

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

    responses to use Kotlin data classes • Convert interfaces from Java to Kotlin • Add tests in Kotlin
  94. • 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
  95. • 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