Advancing Development with Kotlin (Droidcon UK 2015)

Advancing Development with Kotlin (Droidcon UK 2015)

Using Kotlin for Android development has grown in popularity over the last year. Even to those who are not currently using it, the value proposition of the language immediately resonates. There already are a lot of introductory talks to the language and its extensions for use on Android. This talk will explore advancing the usage and design patterns of the language for Android development to solve larger problems.

Prior knowledge or use of Kotlin is not required to attend this talk. Some concepts of the language will be used without introduction but they are intuitive and/or quickly learned. Even if you don't fully understand every language concept on which each example is built, the resulting functionality will be clear.

Video: https://skillsmatter.com/skillscasts/6651-advancing-development-with-the-kotlin-language

E68309f117985270285ade8082f4877d?s=128

Jake Wharton

October 30, 2015
Tweet

Transcript

  1. 5.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time • No streams
  2. 6.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time • No streams • No lambdas, method refs, non-capturing anon class
  3. 7.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time • No streams • No lambdas, method refs, non-capturing anon class • No try-with-resources
  4. 8.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time Use ThreeTenBP / ThreeTenABP • No streams • No lambdas, method refs, non-capturing anon class • No try-with-resources
  5. 9.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time Use ThreeTenBP / ThreeTenABP • No streams Use backport or RxJava • No lambdas, method refs, non-capturing anon class • No try-with-resources
  6. 10.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time Use ThreeTenBP / ThreeTenABP • No streams Use backport or RxJava • No lambdas, method refs, non-capturing anon class Use Retrolambda • No try-with-resources
  7. 11.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • No javax.time Use ThreeTenBP / ThreeTenABP • No streams Use backport or RxJava • No lambdas, method refs, non-capturing anon class Use Retrolambda • No try-with-resources minSdkVersion=19 or Retrolambda
  8. 13.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • Java language restrictions and problems
  9. 15.

    Why do we need advancing? • Java language restrictions and

    problems • Inability to add methods to platform types
  10. 16.

    Why do we need advancing? • Java language restrictions and

    problems • Inability to add methods to platform types ("Util" hell)
  11. 17.

    Why do we need advancing? • Java language restrictions and

    problems • Inability to add methods to platform types ("Util" hell) • Nullability problems
  12. 18.

    Why do we need advancing? • Java language restrictions and

    problems • Inability to add methods to platform types ("Util" hell) • Nullability problems • General verbosity of common idioms
  13. 19.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • Java language restrictions and problems
  14. 20.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • Java language restrictions and problems • Android API design problems
  15. 23.

    Why do we need advancing? • Android API design problems

    • Inheritance party • Nullability everywhere
  16. 24.

    Why do we need advancing? • Android API design problems

    • Inheritance party • Nullability everywhere • Ceremony of APIs
  17. 25.

    Why do we need advancing? • Stuck on Java 6(.5)ish

    • Java language restrictions and problems • Android API design problems
  18. 43.
  19. 45.

    Extension Functions static boolean IsTuesday(this DateTime date) {
 return date.DayOfWeek

    == DayOfWeek.Tuesday;
 } boolean tuesday = date.IsTuesday;
  20. 52.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    // com/example/TuesdayActivity.kt val tuesday = date.isTuesday();
  21. 53.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    // com/example/TuesdayActivity.kt val tuesday = date.isTuesday();
  22. 54.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    // com/example/TuesdayActivity.kt import com.example.util.isTuesday val tuesday = date.isTuesday();
  23. 55.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    final static isTuesday(Ljava/util/Date;)Z L0 ALOAD 0 INVOKEVIRTUAL java/util/Date.getDay ()I ICONST_2 IF_ICMPNE L1 ICONST_1 GOTO L2 L1 ICONST_0 L2 IRETURN
  24. 56.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    static boolean isTuesday(Date date) {
 return date.getDay() == 2;
 }X
  25. 57.
  26. 58.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    @file:JvmName("DateUtils") // com/example/util/DateExtensionsKt.java static boolean isTuesday(Date date) {
 return date.getDay() == 2;
 }X
  27. 59.

    Extension Functions // com/example/util/DateExtensions.kt fun Date.isTuesday() = day == 2

    @file:JvmName("DateUtils") // com/example/util/DateUtils.java static boolean isTuesday(Date date) {
 return date.getDay() == 2;
 }X
  28. 62.

    Extension Functions fun Date.getDay() = 2 fun View.isAttachedToWindow() = if

    (Build.VERSION.SDK_INT < 19) ViewCompat.isAttachedToWindow(this) else super.isAttachedToWindow()
  29. 63.

    Extension Functions fun Date.getDay() = 2 fun View.isAttachedToWindowCompat() = if

    (Build.VERSION.SDK_INT < 19) ViewCompat.isAttachedToWindow(this) else isAttachedToWindow()
  30. 67.

    Extension Functions fun TextView.setTextSize(size: Float, unit: SizeUnit) {
 setTextSize(unit.toPixels(size, getContext()))


    } void setTextSize(float size) void setTextSize(int unit, float size) enum class SizeUnit {
 DP {
 override fun toPixels(context: Context) = // ...
 },
 SP {
 override fun toPixels(context: Context) = // ...
 };
 
 abstract fun toPixels(context: Context): Int
 }
  31. 69.

    Extension Functions String firstName1= null;
 int firstNameColumn = cursor.getColumnIndexOrThrow("first_name");
 if

    (!cursor.isNull(firstNameColumn)) {
 firstName3=2cursor.getString(firstNameColumn);
 }
  32. 70.

    Extension Functions String firstName1= null;
 int firstNameColumn = cursor.getColumnIndexOrThrow("first_name");
 if

    (!cursor.isNull(firstNameColumn)) {
 firstName3=2cursor.getString(firstNameColumn);
 } fun Cursor.getStringOrNull(columnName: String): String? {
 val index = getColumnIndexOrThrow(columnName)
 return if (isNull(index)) null else getString(index)
 }X
 fun Cursor.getString(columnName: String): String = getStringOrNull(columnName)!!
  33. 71.

    Extension Functions fun Cursor.getStringOrNull(columnName: String): String? {
 val index =

    getColumnIndexOrThrow(columnName)
 return if (isNull(index)) null else getString(index)
 }X
 fun Cursor.getString(columnName: String): String = getStringOrNull(columnName)!!
  34. 72.

    Extension Functions fun Cursor.getStringOrNull(columnName: String): String? {
 val index =

    getColumnIndexOrThrow(columnName)
 return if (isNull(index)) null else getString(index)
 }X
 fun Cursor.getString(columnName: String): String = getStringOrNull(columnName)!! val firstName = cursor.getStringOrNull("first_name")
  35. 73.

    Extension Functions fun Cursor.getStringOrNull(columnName: String): String? {
 val index =

    getColumnIndexOrThrow(columnName)
 return if (isNull(index)) null else getString(index)
 }X
 fun Cursor.getString(columnName: String): String = getStringOrNull(columnName)!! val firstName = cursor.getStringOrNull("first_name")
  36. 74.

    Extension Functions fun Cursor.getStringOrNull(columnName: String): String? {
 val index =

    getColumnIndexOrThrow(columnName)
 return if (isNull(index)) null else getString(index)
 }X
 fun Cursor.getString(columnName: String): String = getStringOrNull(columnName)!! val firstName = cursor.getStringOrNull("first_name")
  37. 75.

    Extension Functions fun Cursor.getStringOrNull(columnName: String): String? {
 val index =

    getColumnIndexOrThrow(columnName)
 return if (isNull(index)) null else getString(index)
 }X
 fun Cursor.getString(columnName: String): String = getStringOrNull(columnName)!! val firstName = cursor.getStringOrNull("first_name")
  38. 79.

    Function Expressions { x, y -> x + y }

    { x: Int, y: Int -> x + y } { it.toString() } { x, y -> x + y } { x: Int, y: Int -> x + y }
  39. 80.

    Function Expressions { x, y -> x + y }

    { x: Int, y: Int -> x + y } val sum = { x: Int, y: Int -> x + y } val sum: (Int, Int) -> Int = { x, y -> x + y } { it.toString() }
  40. 81.

    Function Expressions { !it.isEmpty() } { it.length() > 4 }

    { it.matches("\d{4}") } { luhnCheck(it) }
  41. 82.

    Function Expressions val notEmpty: (String) -> Boolean { !it.isEmpty() }

    val atLeastFour: (String) -> Boolean { it.length() > 4 } val fourDigits: (String) -> Boolean { it.matches("\d{4}") } val validCreditCard: (String) -> Boolean { luhnCheck(it) }
  42. 84.

    Higher-Order Functions fun apply(one: Int, two: Int, func: (Int, Int)

    -> Int): Int {
 return func(one, two)
 }
  43. 85.

    Higher-Order Functions fun apply(one: Int, two: Int, func: (Int, Int)

    -> Int): Int {
 return func(one, two)
 } val sum = apply(1, 2, { x, y -> x + y })
  44. 86.

    Higher-Order Functions fun apply(one: Int, two: Int, func: (Int, Int)

    -> Int): Int {
 return func(one, two)
 }X val sum = apply(1, 2, { x, y -> x + y } val difference = apply(1, 2, { x, y -> x - y } ) )
  45. 87.

    Higher-Order Functions fun apply(one: Int, two: Int, func: (Int, Int)

    -> Int): Int {
 return func(one, two)
 }X val sum = apply(1, 2) { x, y -> x + y }
 val difference = apply(1, 2) { x, y -> x - y }
  46. 88.

    Higher-Order Functions fun apply(one: Int, two: Int, func: (Int, Int)

    -> Int): Int {
 return func(one, two)
 }X
  47. 91.

    Higher-Order Functions fun Int.apply(other: Int, func: (Int, Int) -> Int):

    Int {
 return func(this, other)
 } val sum = 1.apply(2) { x, y -> x + y }
 val difference = 1.apply(2) { x, y -> x - y }
  48. 94.

    Higher-Order Functions fun Int.apply(func: (Int) -> Int): Int {
 return

    func(this)
 }X val double = 1.apply { x -> x * 2 }
  49. 95.

    Higher-Order Functions fun Int.apply(func: (Int) -> Int): Int {
 return

    func(this)
 }X val double = 1.apply { it * 2 }
  50. 97.
  51. 98.

    Higher-Order Functions fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {


    val newList = ArrayList<T>()
 for (item in this) {
 }Y
 return newList
 }X
  52. 99.

    Higher-Order Functions fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {


    val newList = ArrayList<T>()
 for (item in this) {
 if (predicate(item)) {
 }Z
 }Y
 return newList
 }X
  53. 100.

    Higher-Order Functions fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {


    val newList = ArrayList<T>()
 for (item in this) {
 if (predicate(item)) {
 newList.add(item)
 }Z
 }Y
 return newList
 }X
  54. 101.

    Higher-Order Functions fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {


    val newList = ArrayList<T>()
 for (item in this) {
 if (predicate(item)) {
 newList.add(item)
 }
 }
 return newList
 } val names = listOf("Jake", "Jesse", "Matt", "Alec")
 val jakes = names.filter { it == "Jake" }

  55. 103.

    Higher-Order Functions fun Any.lock(func: () -> Unit) {
 synchronized (this)

    { func()
 }
 } writeLock.lock { if (!isClosed) { closeWriter() isClosed = true } }
  56. 104.

    Higher-Order Functions data class Lock<T>(private val obj: T) {
 public

    fun acquire(func: (T) -> Unit) {
 synchronized (obj) {
 func(obj)
 }
 }
 }
  57. 105.

    Higher-Order Functions data class Lock<T>(private val obj: T) {
 public

    fun acquire(func: (T) -> Unit) {
 synchronized (obj) {
 func(obj)
 }
 }
 } val readerLock = Lock(JsonReader(stream))
  58. 106.

    Higher-Order Functions data class Lock<T>(private val obj: T) {
 public

    fun acquire(func: (T) -> Unit) {
 synchronized (obj) {
 func(obj)
 }
 }
 } val readerLock = Lock(JsonReader(stream))
 
 // Later
 readerLock.acquire { 
 println(it.readString())
 }
  59. 107.

    Higher-Order Functions val notEmpty: (String) -> Boolean { !it.isEmpty() }

    val atLeastFour: (String) -> Boolean { it.length() > 4 } val fourDigits: (String) -> Boolean { it.matches("\\d{4}") } val validCreditCard: (String) -> Boolean { luhnCheck(it) }
  60. 108.

    Higher-Order Functions val notEmpty: (String) -> Boolean { !it.isEmpty() }

    val atLeastFour: (String) -> Boolean { it.length() > 4 } val fourDigits: (String) -> Boolean { it.matches("\\d{4}") } val validCreditCard: (String) -> Boolean { luhnCheck(it) } firstName.validateWith(notEmpty) lastName.validateWith(notEmpty) username.validateWith(atLeastFour) pin.validateWith(fourDigits) creditCard.validateWith(validCreditCard) { !it.isEmpty() }
  61. 109.

    Higher-Order Functions firstName.validateWith { !it.isEmpty() } lastName.validateWith { !it.isEmpty() }

    username.validateWith { it.length() > 4 } pin.validateWith { it.matches("\d{4}") } creditCard.validateWith { luhnCheck(it) }
  62. 110.

    Higher-Order Functions val notEmpty: (String) -> Boolean { !it.isEmpty() }

    firstName.validateWith(notEmpty) lastName.validateWith(notEmpty) username.validateWith { it.length() > 4 } pin.validateWith { it.matches("\d{4}") } creditCard.validateWith { luhnCheck(it) } { !it.isEmpty() }
  63. 113.

    Extension Function Expressions • Extension Functions — Functions added to

    a type without modifying the original. • Function Expressions — Undeclared function bodies used an as expression (i.e., as data).
  64. 114.

    Extension Function Expressions • Extension Functions — Functions added to

    a type without modifying the original. • Function Expressions — Undeclared function bodies used an as expression (i.e., as data). • Higher-Order Functions: A function which takes a function or returns a function.
  65. 115.

    Extension Function Expressions db.beginTransaction();
 try {
 db.delete("users", "first_name = ?",

    new String[] { "Jake" });
 db.setTransactionSuccessful();
 } finally {
 db.endTransaction();
 }
  66. 116.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: () -> Unit) {
 beginTransaction()


    try {
 func()
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }
 }
  67. 117.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: () -> Unit) {
 beginTransaction()


    try {
 func(
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 }X )
  68. 118.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: () -> Unit) {
 beginTransaction()


    try {
 func(
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 db.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  69. 119.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: (SQLiteDatabase) -> Unit) {
 beginTransaction()


    try {
 func(
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 db.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  70. 120.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: (SQLiteDatabase) -> Unit) {
 beginTransaction()


    try {
 func(this
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 db.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  71. 121.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: (SQLiteDatabase) -> Unit) {
 beginTransaction()


    try {
 func(this
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 it.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  72. 122.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: SQLiteDatabase.() -> Unit) {
 beginTransaction()


    try {
 func(this
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 it.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  73. 123.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: SQLiteDatabase.() -> Unit) {
 beginTransaction()


    try {
 this.func(
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 it.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  74. 124.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: SQLiteDatabase.() -> Unit) {
 beginTransaction()


    try {
 func(
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 it.delete("users", "first_name = ?", arrayOf("Jake"))
 }X )
  75. 125.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: SQLiteDatabase.() -> Unit) {
 beginTransaction()


    try {
 func()
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X db.inTransaction { 
 delete("users", "first_name = ?", arrayOf("Jake"))
 }X
  76. 127.

    Extension Function Expressions NEW MyClassKt$deleteUsers$1 DUP ALOAD 2 INVOKESPECIAL MyClassKt$deleteUsers$1.<init>

    (Ljava/lang/String;)V CHECKCAST kotlin/jvm/functions/Function1 INVOKESTATIC DbExtensionsKt.inTransaction (Landroid/database/sqlite/SQLiteDatabase;Lkotlin/jvm/functions/Function1;)V db.inTransaction { 
 delete("users", "first_name = ?", arrayOf("Jake"))
 }X
  77. 128.

    Extension Function Expressions DbExtensions.inTransaction(db, new Function1<SQLiteDatabase, Unit>() {
 @Override public

    Unit invoke(SQLiteDatabase db) {
 db.delete("users", "first_name = ?", new String[] { "Jake "});
 return null;
 }
 }); db.inTransaction { 
 delete("users", "first_name = ?", arrayOf("Jake"))
 }X
  78. 129.

    Extension Function Expressions fun SQLiteDatabase.inTransaction(func: SQLiteDatabase.() -> Unit) {
 beginTransaction()


    try {
 func()
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X
  79. 130.

    Extension Function Expressions inline fun SQLiteDatabase.inTransaction(func: SQLiteDatabase.() -> Unit) {


    beginTransaction()
 try {
 func()
 setTransactionSuccessful()
 } finally {
 endTransaction()
 }Y
 }X
  80. 131.

    Extension Function Expressions L6 ALOAD 2 INVOKEVIRTUAL com/example/SQLiteDatabase.beginTransaction ()V L0

    NOP L9 ALOAD 2 CHECKCAST com/example/SQLiteDatabase ASTORE 3 L10 ALOAD 3 LDC "users" LDC "first_name = ?" ICONST_1 ANEWARRAY java/lang/String DUP ICONST_0 LDC "Jake" AASTORE CHECKCAST [Ljava/lang/String; INVOKEVIRTUAL com/example/SQLiteDatabase.delete (Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V L11 L12 GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit; POP L13 ALOAD 2 INVOKEVIRTUAL com/example/SQLiteDatabase.setTransactionSuccessful ()V L1 ALOAD 2 INVOKEVIRTUAL com/example/SQLiteDatabase.endTransaction ()V L14 GOTO L15 L2 ASTORE 3 L3 ALOAD 2 INVOKEVIRTUAL com/example/SQLiteDatabase.endTransaction ()V
  81. 132.

    Extension Function Expressions db.beginTransaction();
 try {
 db.delete("users", "first_name = ?",

    new String[] { "Jake "});
 db.setTransactionSuccessful();
 } finally {
 db.endTransaction();
 }X
  82. 133.

    Extension Function Expressions db.beginTransaction();
 try {
 db.delete("users", "first_name = ?",

    new String[] { "Jake "});
 db.setTransactionSuccessful();
 } finally {
 db.endTransaction();
 }X db.inTransaction { 
 delete("users", "first_name = ?", arrayOf("Jake"))
 }X + inline fun =
  83. 134.

    Extension Function Expressions inline fun notification(context: Context, func: Notification.Builder.() ->

    Unit): Notification {
 val builder = Notification.Builder(context)
 builder.func()
 return builder.build()
 }
  84. 135.

    Extension Function Expressions inline fun notification(context: Context, func: Notification.Builder.() ->

    Unit): Notification {
 val builder = Notification.Builder(context)
 builder.func()
 return builder.build()
 } val n = notification(context) {
 setContentTitle("Hi")
 setSubText("Hello")
 }
  85. 136.

    Extension Function Expressions verticalLayout {
 padding = dip(30)
 editText {


    hint = "Name"
 textSize = 24f
 }
 editText {
 hint = "Password"
 textSize = 24f
 }
 button("Login") {
 textSize = 26f
 }
 }