exploring koltin on android
TRANSCRIPT
What is Kotlin?• Statically typed programming language
• JVM-based language developed by JetBrains
• From industry not academia
• 100% inter-operable with the Java language
Open Source - https://github.com/JetBrains/kotlin
Why Kotlin
Concise - Drastically reduce the amount of boilerplate code you need to write.
Safe - Avoid entire classes of errors such as null pointer exceptions.
Versatile - Build server-side applications, Android apps or front-end code running in the browser.
Kotlin in Expedia Android
Kotlin code is at 23% and rising.
Refactoring started mid 2015.
Hotels, Packages, Flights, Services and MockWebServer & tests classes are 90% in Kotlin
Cars/LX/Legacy tablet code still in java.
Other teams in Expedia are experimenting with Kotlin in the web.
Features• Null type and safety
• Lambdas
• Optional params
• Data classes
• Extension functions
• Delegates
• Smart Casting
Kotlin Roadmap for Android
• Incremental compilation
• support for Jack and Jill toolchain
• Instant run works for cold swaps right now not for hot swaps
• Lint checks in Kotlin 1.0.2
fun printSum(a: Int, b: Int): Unit { print(a + b)}
fun printSum(a: Int, b: Int) { print(a + b)}
Defining Functions
fun doSomething(): Int { val a: Int = 1 val b = 1 val c: Int c = a + b return c}
Defining local variables
fun doSomething(): Int { val a: Int = 1 var b = 1 val c: Int
b += 5 c = a + b return c}
Defining local variables
Using conditional expressions
fun doSomething(a: Int, b: Int): Int { if (a > b) return a else return b }
Using conditional expressions
fun doSomething(a: Int, b: Int): Int { if (a > b) return a else return b}
fun doSomething(a: Int, b: Int) = if (a > b) a else b
Using a for loopfun doSomething(args: Array<String>) { for (arg in args) print(arg)}
fun doSomething(args: Array<String>) { for (i in args.indices) print(args[i])}
Using when expressionfun doSomething(obj: Any) { when (obj) { 1 -> print("One") "Hello" -> print("Kotlin") is Long -> print("Long") !is String -> print(“!string") else -> print("Unknown") }}
Using rangesfun doSomething(x: Int, y: Int) { if (x in 1..y - 1) print("In") for (x in 1..5) print(“N")
if (x !in 1..y - 1) print(“In")
}
Using rangesfun doSomething(x: Int, y: Int) { if (x in 1..y - 1) print("In") for (x in 1..5) print("N") if (x !in 1..y - 1) print(“In") }
Using rangesfun doSomething(x: Int, y: Int) { if (x in 1..y - 1) print("In") for (x in 1..5) print("N") if (x !in 1..y - 1) print(“In") }
Class in Javapublic class SomeClass { private String variable; private final String defaultVar; SomeClass(String variable) { this.variable = variable; this.defaultVar = "Java"; } SomeClass(String variable, String defaultVar) { this.variable = variable; this.defaultVar = defaultVar; } }
// Use new SomeClass("Kotlin", "Java"); new SomeClass("Kotlin");
Class in Kotlin
class SomeClass(var variable: String, val defaultValue: String = "Java") {}
// UseSomeClass("Kotlin", "Java") SomeClass("Kotlin")
Lambdas//In Kotlin
fun doSomething() { val items = ArrayList<String>() items.sortBy { it.length } }
//In Java
ArrayList<String> items = new ArrayList(); Collections.sort(items, new Comparator<String>(){ @Override public int compare(String s1, String s2) { return s1.length() - s2.length(); } });
button.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { Toast.makeText(this.MainActivity, "Button", Toast.LENGTH_LONG).show(); } });
In Java
button.setOnClickListener(object: View.OnClickListener { override fun onClick(view: View): Unit { Toast.makeText(context, "Button", Toast.LENGTH_LONG).show() }})
In Kotlin
button.setOnClickListener { view -> Toast.makeText(this, "Button", Toast.LENGTH_LONG).show()}
In Kotlin
//In Kotlin
button.setOnClickListener { Toast.makeText(this, "Button", Toast.LENGTH_LONG).show()}
//In Java
button.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { Toast.makeText(this.MainActivity, "Button", Toast.LENGTH_LONG).show(); } });
class Vehicle(var value: String) {} fun doSomething() { val type = Vehicle("Car") println("${type.value}")}
class Vehicle(var value: String) {} fun doSomething() { val type = Vehicle("Car") println("${type.value}")}
class Vehicle(var value: String) {} fun doSomething() { val type = Vehicle("Car") println("${type.value}") }
class Vehicle(var value: String) { fun print() = println("$value") } fun doSomething() { val type = Vehicle("Car") type.print()}
class Vehicle(var value: String) { fun print() = println("$value") } fun doSomething() { val type = Vehicle("Car") type.print()}
class Vehicle(var value: String) { fun print() = println("$value")} fun doSomething() { val types = listOf( Vehicle("Car"), Vehicle("Truck"), Vehicle("Bike")) }
class Vehicle(var value: String) { fun print() = println("$value")} fun doSomething() { val types = listOf( Vehicle("Car"), Vehicle("Truck"), Vehicle("Bike")) for (type in types) { type.print() }}
class Vehicle(var value: String) { fun print() = println("$value")} fun doSomething() { val types = listOf( Vehicle("Car"), Vehicle("Truck"), Vehicle("Bike")) types.forEach { type -> type.print() } }
class Vehicle(var value: String) { fun print() = println("$value")} fun doSomething() { val types = listOf( Vehicle("Car"), Vehicle("Truck"), Vehicle("Bike")) types.forEach { it.print() } }
class Vehicle(var value: String) { fun print() = println("$value") } fun doSomething() { val types = listOf( Vehicle("Car"), Vehicle("Truck"), Vehicle("Bike")) types.forEach { it.print() } }
class Vehicle(var value: String) { fun print() = println("$value") } fun doSomething() { listOf( Vehicle("Car"), Vehicle("Truck"), Vehicle("Bike") ).forEach { it.print() } }
open class Vehicle(var value: String) { fun print() = println("$value")} class Car(var type: String) : Vehicle(type){} fun doSomething() { listOf( Car("Car"), Vehicle("Truck"), Vehicle("Bike") ).forEach{it.print()} }
open class Vehicle(var value: String) { fun print() = println("$value") } class Car(var type: String) : Vehicle(type){} fun doSomething() { listOf( Car("Car"), Vehicle("Truck"), Vehicle("Bike") ).forEach{it.print()} }
open class Vehicle(var value: String) { open fun print() = println("$value") } class Car(var type: String) : Vehicle(type){ override fun print() = println("Yo, Its a $value") } fun doSomething() { listOf( Car("Car"), Vehicle("Truck"), Vehicle("Bike") ).forEach{it.print()} }
open class Vehicle(var value: String) { open fun print() = println("$value") } class Car(var type: String) : Vehicle(type){ override fun print() = println("Yo, Its a $value") } fun doSomething() { listOf( Car("Car"), Vehicle("Truck"), Vehicle("Bike") ).forEach{it.print()} }
Nullability I call it my billion-dollar mistake. It was the invention of the null reference …. has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
-Sir Charles Antony Richard Hoare
fun doSomething() { var a: String = "abc" a = null // compilation error var b: String? = "abc" b = null // ok}
Null and type safety
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l = a.length}
Null and type safety
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l1 = a.length val l2 = b.length // error:variable 'b' can be null}
Null and type safety
Checking for null in conditions
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l1 = a.length val l = if (b != null) b.length else -1 }
Safe Calls
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l1 = a.length val l2 = b?.length }
Elvis Operator
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l1 = a.length val l = if (b != null) b.length else -1 }
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l1 = a.length val l2 = b?.length ?: -1 }
if (originLocation != null) { if (originLocation.hierarchyInfo != null) { if (originLocation.hierarchyInfo.airport != null) { if (originLocation.hierarchyInfo.airport.airportCode != null) { departureAirportCode = originLocation.hierarchyInfo.airport.airportCode; } } } } else { departureAirportCode = ""; }
val departureAirportCode = originLocation?.hierarchyInfo?.airport?.airportCode ?: ""
if (originLocation != null) { if (originLocation.hierarchyInfo != null) { if (originLocation.hierarchyInfo.airport != null) { if (originLocation.hierarchyInfo.airport.airportCode != null) { departureAirportCode = originLocation.hierarchyInfo.airport.airportCode; } } } } else { departureAirportCode = "";}
The !! Operator
fun doSomething() { var a: String = "abc" var b: String? = "abc" b = null // ok val l1 = a.length val l2 = b!!.length }
Optional-params //In Java
void foo(int p1, boolean p2) { } void foo(int p1) { foo(1, false); } void foo() { foo(1, false); }
Data class
data class Traveler(val name: String?, val age: Int)
Automatically implements:
equals()/hashCode()toString() of the form "User(name=John, age=42)", copy() function
val testTravlerJohn = Traveler(“John”, 42) val testTravlerJane = Traveler(“Jane”, 41)
assertEquals(testTravlerJohn, testTravlerJane)
Data class in Testing
public class Traveler(String name, Int age)
@Overridepublic boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Traveler)) { return false; } Traveler traveler = (Traveler) o; if (name != name) { return false; } if (age != traveler.age) { return false; }
return true; }
Extension Functions//In Java public class StrUtils { public static String encodeString(String str) { return str.replaceAll(" ", "_") } }
String text = "Hello World"; System.out.println(StrUtils.encodeString(text));
Extension Functions//In Kotlin fun String.encodeSpaces(): String { return this.replaceAll(" ", "_") }
val text = "hello world"println(text.encodeSpaces())
val list = listOf("E", "H", "Hello", "Hi", "I") val map = mapOf(Pair("A", 1), Pair("B", 2), Pair("C", 3)) list.forEach { s -> s.length}
"E, H, Hello, Hi, I"
Kotlin Collections Extensions
val list = listOf("E", "H", "Hello", "Hi", "I")val map = mapOf(Pair("A", 1), Pair("B", 2), Pair("C", 3))var count = 0 list.forEachIndexed { i, s -> count += i s.length} "E, H, Hello, Hi, I"
val list = listOf("E", "H", "Hello", "Hi", "I")val map = mapOf(Pair("A", 1), Pair("B", 2), Pair("C", 3))list.filter { s -> s.contains("H") } "H, Hello, Hi"
val list = listOf("E", "H", "Hello", "Hi", "I")val map = mapOf(Pair("A", 1), Pair("B", 2), Pair("C", 3))list.first { s -> s.startsWith("He") } "Hello"
val list = listOf("E", "H", "Hello", "Hi", "I")val map = mapOf(Pair("A", 1), Pair("B", 2), Pair("C", 3))map.forEach { entry -> entry.value } "A, B, C"
//In Java final int[] sponsoredIndexes = { 0, 50, 51 }; ArrayList<Property> sponsored = new ArrayList<Property>(); ListIterator<Property> it = properties.listIterator(); while (it.hasNext()) { Property prop = it.next(); if (prop.isSponsored()) { it.remove(); sponsored.add(prop); } } for (int i = 0; i < sponsored.size() && i < sponsoredIndexes.length; i++) { if (sponsoredIndexes[i] <= properties.size()) { properties.add(sponsoredIndexes[i], sponsored.get(i)); } }
//In Kotlin fun putSponsoredItemsInCorrectPlaces(hotelList: List<Hotel>): List<Hotel> { val (sponsored, nonSponsored) = hotelList.partition { it.isSponsoredListing } val firstChunk = sponsored.take(1) val secondChunk = nonSponsored.take(49) val thirdChunk = sponsored.drop(1) val rest = nonSponsored.drop(49) return firstChunk + secondChunk + thirdChunk + rest}
//In Java Observable.just("Hello World") .map(new Func1<String, Object>() { @Override public Object call(String s) { return s + "!"; } }) .subscribe(new Subscriber<String>() { @Override public void onCompleted() { //Completion } @Override public void onError(final Throwable e) { //TODO : Handle error here } @Override public void onNext(final String s) { Log.e("Output",s); } });
Reative Java
Kotlin & Reactive
Observable.just("Hello World") .map { it.plus("!") }
.subscribe { println("Output:" + it); }
Flow sensitive typing//In Java
@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder.getItemViewType() != LOADING_VIEW) { ((ViewHolder) holder).updateHotel(); } else if (holder.getItemViewType() != HOTEL_VIEW) { ((LoadingViewHolder) holder).loadView(); } }
Flow sensitive typing//In Kotlin
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is HotelCellViewHolder-> holder.updateHotel() is LoadingViewHolder -> holder.loadView() }}
Delegatesvar p: String by Delegate()
Properties using the delegate expression will have its get() and set() methods delegated to the expression.
* lazy
* observable
* notNull
Delegates.notNull
class Foo { var bar : Bar by Delegates.notNull() // type is now Bar init { val s = bar // Using before setting throws an IllegalStateException! bar = Bar() }}
Delegates.observable
val text: String by Delegates.observable("") { prop, old, new -> println("$old -> $new") }
Custom delegate(KotterKnife)
fun <T : View> ViewGroup.bindView(id: Int): ReadOnlyProperty<Any, T> = ViewBinding(this, id)
private class ViewBinding<T : View>(val source: Any, val id: Int) : ReadOnlyProperty<Any, T> { private val lazy = Lazy<T>() override fun getValue(thisRef: Any, property: KProperty<*>): T = lazy.get { findView<T>(source, id) ?: throw IllegalStateException("View ID $id for '${property.name}' not found.") } }
val mapView: MapView by bindView(R.id.map_view)