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”
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
val name: String = “Segun” val email: String? = null name.length // works fine email.length // compiler error (unsafe access) email?.length // works fine (safe access)
// 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 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)
// 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”
// 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)
// 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
// Data holding class in Java public final class User { ... public User(int id, String email){...} // Getters public final int getId() {...} public final String getEmail() {...} }
// 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 )
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) {...} ... }
// 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 )
// Person data class data class Person(val name: String, val age: Int) // Generated functions public final String component1() {...} // name public final int component2() {...} // age
// 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
// 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()
// 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) {...}
// 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) {...}
// 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() }
// 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() }
// 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() }
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
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”
Higher order function fun atLeastAndroidO(action: () -> Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { action() } } // Usage: atLeastAndroidO { // do something requiring Android O }
// 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
// Kotlin higher order function inline fun atLeastAndroidO(action: () -> Unit) {...} // Use like this: fun doSomethingOnOreo() { atLeastAndroidO { println("Hey, running on O") } }
// 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"); } }
● 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:
● 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?
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)
operator fun ViewGroup.iterator() = object : MutableIterator { private var index = 0 override fun hasNext() = index < childCount override fun next() = getChildAt(index++) override fun remove() = removeViewAt(--index) }
operator fun ViewGroup.iterator() = object : MutableIterator { private var index = 0 override fun hasNext() = index < childCount override fun next() = getChildAt(index++) override fun remove() = removeViewAt(--index) } Extension function on ViewGroup class!
● 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() ...
// 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
data class ShoppingCart(val items: MutableList) TODO("Ability to add a new product to the shopping cart") val cart = createEmptyCart() // one way to go cart.items.add(product)
data class ShoppingCart(val items: MutableList) TODO("Ability to add a new product to the shopping cart") val cart = createEmptyCart() // one way to go cart.items.add(product)
data class ShoppingCart(val items: MutableList) { operator fun plusAssign(product: Product) { items.add(product) } } // Usage of the plusAssign operator function val cart = createEmptyCart() cart += Product(price = 12.00)
// Usage of the plusAssign operator function val cart = createEmptyCart() cart += Product(price = 12.00) data class ShoppingCart(val items: MutableList) { operator fun plusAssign(product: Product) { items.add(product) } }
... 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:
Example of this in Android framework code! // Support library < 27 class Fragment { ... final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } }
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 = ...
Example of this in Android framework code! // Support library >= 27 class Fragment { ... @Nullable final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } }
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 = ...
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 = ...
// 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)
// 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)
// 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)
class Data(val value: Int...) : Parcelable { private constructor(parcel: Parcel)... ... companion object { val CREATOR: Parcelable.Creator = ... } } 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.
class Data(val value: Int...) : Parcelable { private constructor(parcel: Parcel)... ... companion object { @JvmField val CREATOR: Parcelable.Creator = ... } } CREATOR is exposed as a field, and everything is good!
// 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() }
● 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
● 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
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