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

Android KTX (Google IO 2018)

Android KTX (Google IO 2018)

This talk introduces Android KTX, which is the new set of Kotlin extensions for Android. It demonstrates how Android developers writing Kotlin can use Android KTX to make their code more idiomatic, concise, and pleasant. It will also discuss how developers can make their own libraries more Kotlin-friendly.

Video: https://youtu.be/st1XVfkDWqk

Jake Wharton

May 10, 2018
Tweet

More Decks by Jake Wharton

Other Decks in Programming

Transcript

  1. Android KTX
    Jake Wharton

    View full-size slide

  2. Android KTX
    Jake Wharton

    View full-size slide

  3. val userLayout: ViewGroup = findViewById(R.id.users)
    for (index in 0 until userLayout.childCount) {
    val view = userLayout.getChildAt(index)
    // Do something with index and view…
    }

    View full-size slide

  4. fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    val userLayout: ViewGroup = findViewById(R.id.users)
    for (index in 0 until userLayout.childCount) {
    val view = userLayout.getChildAt(index)
    // Do something with index and view…
    }A

    View full-size slide

  5. fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    val userLayout: ViewGroup = findViewById(R.id.users)
    for (index in 0 until userLayout.childCount) {
    val view = userLayout.getChildAt(index)
    // Do something with index and view…
    }A

    View full-size slide

  6. fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }D
    val userLayout: ViewGroup = findViewById(R.id.users)
    for (index in 0 until userLayout.childCount) {G
    val view = userLayout.getChildAt(index)
    // Do something with index and view…
    }A

    View full-size slide

  7. fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }D
    val userLayout: ViewGroup = findViewById(R.id.users)
    userLayout.forEachIndexed {Gindex, view ->
    // Do something with index and view…
    }A
    for ( in 0 until .childCount)
    val = userLayout.getChildAt(index)

    View full-size slide

  8. fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }D
    val userLayout: ViewGroup = findViewById(R.id.users)
    userLayout.forEachIndexed {Gindex, view ->
    // Do something with index and view…
    }A

    View full-size slide

  9. fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }D
    val userLayout: ViewGroup = findViewById(R.id.users)
    userLayout.forEachIndexed {Gindex, view ->
    // Do something with index and view…
    }A

    View full-size slide

  10. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }D
    val userLayout: ViewGroup = findViewById(R.id.users)
    userLayout.forEachIndexed {Gindex, view ->
    // Do something with index and view…
    }A

    View full-size slide

  11. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }D
    val userLayout: ViewGroup = findViewById(R.id.users)
    userLayout.forEachIndexed {Gindex, view ->
    // Do something with index and view…
    }A

    View full-size slide

  12. // In an Activity, on API 23+…
    val notifications = getSystemService(NotificationManager::class.java)

    View full-size slide

  13. // In an Activity, on API 23+…
    val notifications = getSystemService(NotificationManager::class.java)
    // or all API levels…
    val notifications = ContextCompat.getSystemService(this,
    NotificationManager::class.java)

    View full-size slide

  14. // In an Activity, on API 23+…
    val notifications = getSystemService(NotificationManager::class.java)
    // or all API levels…
    val notifications = ContextCompat.getSystemService(this,
    NotificationManager::class.java)
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)

    View full-size slide

  15. inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    // In an Activity, on API 23+…
    val notifications = getSystemService(NotificationManager::class.java)
    // or all API levels…
    val notifications = ContextCompat.getSystemService(this,
    NotificationManager::class.java)

    View full-size slide

  16. inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    // In an Activity, on API 23+…
    val notifications = getSystemService(NotificationManager::class.java)
    // or all API levels…
    val notifications = ContextCompat.getSystemService(this,
    NotificationManager::class.java)

    View full-size slide

  17. inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    // In an Activity, on API 23+…
    val notifications = getSystemService(NotificationManager::class.java)H
    // or all API levels…
    val notifications = ContextCompat.getSystemService(this,
    NotificationManager::class.java)
    s

    View full-size slide

  18. inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    // In an Activity…
    val notifications = systemService()H
    S

    View full-size slide

  19. inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    // In an Activity…
    val notifications = systemService()H

    View full-size slide

  20. avatarView.setPadding(
    10, avatarView.paddingTop, 10, avatarView.paddingBottom)

    View full-size slide

  21. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    avatarView.setPadding(
    10, avatarView.paddingTop, 10, avatarView.paddingBottom)

    View full-size slide

  22. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    avatarView.setPadding(
    10, avatarView.paddingTop, 10, avatarView.paddingBottom)

    View full-size slide

  23. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.setPadding(
    10, avatarView.paddingTop, 10, avatarView.paddingBottom)R
    update

    View full-size slide

  24. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(10, 10)R

    View full-size slide

  25. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(10, 10)R

    View full-size slide

  26. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  27. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  28. val rect = avatarView.clipBounds
    val left = rect.left
    val top = rect.top
    val right = rect.right
    val bottom = rect.bottom
    // Use left, top, right, bottom…

    View full-size slide

  29. val rect = avatarView.clipBounds
    val left = rect.left
    val top = rect.top
    val right = rect.right
    val bottom = rect.bottom
    // Use left, top, right, bottom…
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom

    View full-size slide

  30. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val rect = avatarView.clipBounds
    val left = rect.left
    val top = rect.top
    val right = rect.right
    val bottom = rect.bottom
    // Use left, top, right, bottom…

    View full-size slide

  31. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val rect = avatarView.clipBounds
    val left = rect.left
    val top = rect.top
    val right = rect.right
    val bottom = rect.bottom
    // Use left, top, right, bottom…

    View full-size slide

  32. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val rect = avatarView.clipBounds
    val left = rect.left
    valTtop = rect.top
    valRright = rect.right
    valBbottom = rect.bottom
    // Use left, top, right, bottom…

    View full-size slide

  33. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val (left,Ttop,Rright,Bbottom) = avatarView.clipBounds
    // Use left, top, right, bottom…

    View full-size slide

  34. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val (left,Ttop,Rright) = avatarView.clipBounds
    // Use left, top, right…

    View full-size slide

  35. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val (left,T_,Rright) = avatarView.clipBounds
    // Use left, right…

    View full-size slide

  36. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val (left,T_,Rright) = avatarView.clipBounds
    // Use left, right…

    View full-size slide

  37. var onlyDigits = true
    for (c in phoneNumber) {A
    if (!c.isDigit()) {
    onlyDigits = false
    break
    }G
    }B

    View full-size slide

  38. r true
    for (c in )
    if (!c. ) {
    onlyDigits = false
    break
    }G
    val onlyDigits = phoneNumber.all {Ait.isDigit() }B

    View full-size slide

  39. .all {Ait.isDigit() }B
    val onlyDigits = TextUtils.isDigitsOnly(phoneNumber)

    View full-size slide

  40. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = TextUtils.isDigitsOnly(phoneNumber)L

    View full-size slide

  41. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.isDigitsOnly()L

    View full-size slide

  42. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.

    View full-size slide

  43. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.

    View full-size slide

  44. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.is

    View full-size slide

  45. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.is

    View full-size slide

  46. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.isDigitsOnly()L

    View full-size slide

  47. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z

    View full-size slide

  48. core-ktx
    Android KTX

    View full-size slide

  49. Android KTX
    core-ktx Android framework

    View full-size slide

  50. Android KTX
    Android framework
    support-compat
    core-ktx

    View full-size slide

  51. Android KTX
    Android framework
    core
    core-ktx

    View full-size slide

  52. Android KTX
    Android framework
    core
    core-ktx
    fragment-ktx fragment
    palette-ktx palette
    collection-ktx collection
    lifecycle-reactivestreams-ktx lifecycle-reactivestreams
    sqlite-ktx sqlite
    navigation-*-ktx navigation-*
    work-runtime-ktx work-runtime

    View full-size slide

  53. Android KTX
    Android framework
    core
    core-ktx
    fragment-ktx fragment
    palette-ktx palette
    collection-ktx collection
    lifecycle-reactivestreams-ktx lifecycle-reactivestreams
    sqlite-ktx sqlite
    navigation-*-ktx navigation-*
    work-runtime-ktx work-runtime

    View full-size slide

  54. @RequiresApi(26)
    operator fun Color.plus(c: Color): Color {
    val s = if (colorSpace != c.colorSpace) c.convert(colorSpace) else c
    val src = s.components
    val dst = components
    var sa = s.alpha()
    // Destination alpha pre-composited
    var da = alpha() * (1.0f - sa)
    // Index of the alpha component
    val ai = componentCount - 1
    // Final alpha: src_alpha + dst_alpha * (1 - src_alpha)
    dst[ai] = sa + da
    // Divide by final alpha to return non pre-multiplied color
    if (dst[ai] > 0) {
    sa /= dst[ai]
    da /= dst[ai]
    }
    // Composite non-alpha components
    for (i in 0 until ai) {
    dst[i] = src[i] * sa + dst[i] * da
    }
    return Color.valueOf(dst, colorSpace)
    }

    View full-size slide

  55. @RequiresApi(26)
    operator fun Color.plus(c: Color): Color {
    val s = if (colorSpace != c.colorSpace) c.convert(colorSpace) else c
    val src = s.components
    val dst = components
    var sa = s.alpha()
    // Destination alpha pre-composited
    var da = alpha() * (1.0f - sa)
    // Index of the alpha component
    val ai = componentCount - 1
    // Final alpha: src_alpha + dst_alpha * (1 - src_alpha)
    dst[ai] = sa + da
    // Divide by final alpha to return non pre-multiplied color
    if (dst[ai] > 0) {
    sa /= dst[ai]
    da /= dst[ai]
    }
    // Composite non-alpha components
    for (i in 0 until ai) {
    dst[i] = src[i] * sa + dst[i] * da
    }
    return Color.valueOf(dst, colorSpace)
    }

    View full-size slide

  56. @RequiresApi(26)
    operator fun Color.plus(c: Color): Color {
    val s = if (colorSpace != c.colorSpace) c.convert(colorSpace) else c
    val src = s.components
    val dst = components
    var sa = s.alpha()
    // Destination alpha pre-composited
    var da = alpha() * (1.0f - sa)
    // Index of the alpha component
    val ai = componentCount - 1
    // Final alpha: src_alpha + dst_alpha * (1 - src_alpha)
    dst[ai] = sa + da
    // Divide by final alpha to return non pre-multiplied color
    if (dst[ai] > 0) {
    sa /= dst[ai]
    da /= dst[ai]
    }
    // Composite non-alpha components
    for (i in 0 until ai) {
    dst[i] = src[i] * sa + dst[i] * da
    }
    return Color.valueOf(dst, colorSpace)
    }

    View full-size slide

  57. public final class ColorUtils {
    }Z

    View full-size slide

  58. public final class ColorUtils {
    @ColorInt
    public static int compositeColors(
    @ColorInt int foreground, @ColorInt int background) {
    // …
    }X
    }Z

    View full-size slide

  59. public final class ColorUtils {
    @ColorInt
    public static int compositeColors(
    @ColorInt int foreground, @ColorInt int background) {
    // …
    }X
    @RequiresApi(26)
    public static Color compositeColors(
    Color foreground, Color background) {
    // …
    }Y
    }Z

    View full-size slide

  60. @RequiresApi(26)
    operator fun Color.plus(c: Color): Color {
    val s = if (colorSpace != c.colorSpace) c.convert(colorSpace) else c
    val src = s.components
    val dst = components
    var sa = s.alpha()
    // Destination alpha pre-composited
    var da = alpha() * (1.0f - sa)
    // Index of the alpha component
    val ai = componentCount - 1
    // Final alpha: src_alpha + dst_alpha * (1 - src_alpha)
    dst[ai] = sa + da
    // Divide by final alpha to return non pre-multiplied color
    if (dst[ai] > 0) {
    sa /= dst[ai]
    da /= dst[ai]
    }
    // Composite non-alpha components
    for (i in 0 until ai) {
    dst[i] = src[i] * sa + dst[i] * da
    }
    return Color.valueOf(dst, colorSpace)
    }

    View full-size slide

  61. @RequiresApi(26)
    inline operator fun Color.plus(c: Color): Color =
    ColorUtils.compositeColors(c, this)

    View full-size slide

  62. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z

    View full-size slide

  63. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z

    View full-size slide

  64. KTX Principles

    View full-size slide

  65. KTX Principles
    Adapt existing functionality and redirect features upstream

    View full-size slide

  66. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z

    View full-size slide

  67. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z

    View full-size slide

  68. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }
    inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }
    inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z

    View full-size slide

  69. operator fun ViewGroup.iterator() = object : MutableIterator {
    private var index = 0
    override fun hasNext() = index < childCount
    override fun next() =
    getChildAt(index++) ?: throw IndexOutOfBoundsException()
    override fun remove() = removeViewAt(--index)
    }

    View full-size slide

  70. operator fun ViewGroup.iterator() = object : MutableIterator {
    private var index = 0
    override fun hasNext() = index < childCount
    override fun next() =
    getChildAt(index++) ?: throw IndexOutOfBoundsException()
    override fun remove() = removeViewAt(--index)
    }
    for (view in userLayout) {
    // Do something with view…
    }

    View full-size slide

  71. operator fun ViewGroup.iterator() = object : MutableIterator {
    private var index = 0
    override fun hasNext() = index < childCount
    override fun next() =
    getChildAt(index++) ?: throw IndexOutOfBoundsException()
    override fun remove() = removeViewAt(--index)
    }
    for (view in userLayout) {
    // Do something with view…
    }

    View full-size slide

  72. operator fun ViewGroup.iterator() = object : MutableIterator {
    private var index = 0
    override fun hasNext() = index < childCount
    override fun next() =
    getChildAt(index++) ?: throw IndexOutOfBoundsException()
    override fun remove() = removeViewAt(--index)
    }
    for (view in userLayout) {
    // Do something with view…
    }

    View full-size slide

  73. KTX Principles
    Adapt existing functionality and redirect features upstream

    View full-size slide

  74. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive

    View full-size slide

  75. inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
    for (index in 0 until childCount) {
    action(index, getChildAt(index))
    }Z
    }Z
    val userLayout: ViewGroup = findViewById(R.id.users)
    userLayout.forEachIndexed { index, view ->
    // Do something with index and view…
    }A

    View full-size slide

  76. inline fun Context.systemService() =
    ContextCompat.getSystemService(this, T::class.java)
    // In an Activity…
    val notifications = systemService()H

    View full-size slide

  77. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  78. inline operator fun Rect.component1() = left
    inline operator fun Rect.component2() = top
    inline operator fun Rect.component3() = right
    inline operator fun Rect.component4() = bottom
    val (left,T_,Rright) = avatarView.clipBounds
    // Use left, right…

    View full-size slide

  79. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)Z
    val onlyDigits = phoneNumber.isDigitsOnly()L

    View full-size slide

  80. operator fun ViewGroup.iterator() = object : MutableIterator {
    private var index = 0
    override fun hasNext() = index < childCount
    override fun next() =
    getChildAt(index++) ?: throw IndexOutOfBoundsException()
    override fun remove() = removeViewAt(--index)
    }
    for (view in userLayout) {
    // Do something with view…
    }

    View full-size slide

  81. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive

    View full-size slide

  82. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive
    Leverage features unique to Kotlin

    View full-size slide

  83. view.setOnClickListener {
    // React to click…
    }Z

    View full-size slide

  84. fun View.click(listener: (View) -> Unit) {
    setOnClickListener(listener)
    }Y
    view.setOnClickListener {P
    // React to click…
    }Z

    View full-size slide

  85. fun View.click(listener: (View) -> Unit) {
    setOnClickListener(listener)
    }Y
    view.setOnClickListener {P
    // React to click…
    }Z
    c

    View full-size slide

  86. fun View.click(listener: (View) -> Unit) {
    setOnClickListener(listener)
    }Y
    view.click {P
    // React to click…
    }Z
    C

    View full-size slide

  87. view.click {P
    // React to click…
    }Z
    view.setOnClickListener {
    // React to click…
    }

    View full-size slide

  88. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive
    Leverage features unique to Kotlin

    View full-size slide

  89. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive
    Leverage features unique to Kotlin
    Code golf APIs to be as short as possible

    View full-size slide

  90. if (Build.VERSION.SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  91. inline fun onApi(level: Int, body: () -> Unit) {
    if (Build.VERSION.SDK_INT >= level) {
    body()
    }Z
    }
    if (Build.VERSION.SDK_INT > 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  92. inline fun onApi(level: Int, body: () -> Unit) {
    if (Build.VERSION.SDK_INT >= level) {
    body()
    }Z
    }Q
    if (Build.VERSION.SDK_INT > 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z
    onApi

    View full-size slide

  93. inline fun onApi(level: Int, body: () -> Unit) {
    if (Build.VERSION.SDK_INT >= level) {
    body()
    }Z
    }Q
    onApi(19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  94. onApi(19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  95. onApi(19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z
    if (Build.VERSION.SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  96. if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z
    onApi(19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  97. if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z
    onApi(19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  98. if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Zelse {
    // Something…
    }
    onApi(19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Z

    View full-size slide

  99. if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Zelse {
    // Something…
    }L
    onApi(19)F{A
    TransitionManager.beginDelayedTransition(viewGroup)
    },

    View full-size slide

  100. if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Zelse {
    // Something…
    }L
    onApi(19, {A
    TransitionManager.beginDelayedTransition(viewGroup)
    }, {T
    // Something…
    })F

    View full-size slide

  101. if (SDK_INT >= 28) {
    // Fancy new thing…
    } else if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Zelse {
    // Something…
    }L
    onApi(19, {A
    TransitionManager.beginDelayedTransition(viewGroup)
    },Z{T
    // Something…
    })F

    View full-size slide

  102. if (SDK_INT >= 28) {
    // Fancy new thing…
    } else if (SDK_INT >= 19) {
    TransitionManager.beginDelayedTransition(viewGroup)
    }Zelse {
    // Something…
    }L
    onApi(19, {A
    TransitionManager.beginDelayedTransition(viewGroup)
    },Z{T
    // Something…
    })F

    View full-size slide

  103. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive
    Leverage features unique to Kotlin
    Code golf APIs to be as short as possible

    View full-size slide

  104. KTX Principles
    Adapt existing functionality and redirect features upstream
    Default to inline unless code size or allocation is prohibitive
    Leverage features unique to Kotlin
    Code golf APIs to be as short as possible
    Optimize for a single and/or specific use case

    View full-size slide

  105. Android KTX
    Android framework
    core
    core-ktx
    fragment-ktx fragment
    palette-ktx palette
    collection-ktx collection
    lifecycle-reactivestreams-ktx lifecycle-reactivestreams
    sqlite-ktx sqlite
    navigation-*-ktx navigation-*
    work-runtime-ktx work-runtime

    View full-size slide

  106. Android KTX
    Android framework
    core
    core-ktx
    fragment-ktx fragment
    palette-ktx palette
    collection-ktx collection
    lifecycle-reactivestreams-ktx lifecycle-reactivestreams
    sqlite-ktx sqlite
    navigation-*-ktx navigation-*
    work-runtime-ktx work-runtime

    View full-size slide

  107. supportFragmentManager.beginTransaction()
    .replace(android.R.id.content, userFragment)
    .commit()

    View full-size slide

  108. inline fun FragmentManager.transaction(
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    transaction.commit()
    }
    supportFragmentManager.beginTransaction()
    .replace(android.R.id.content, userFragment)
    .commit()

    View full-size slide

  109. inline fun FragmentManager.transaction(
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    transaction.commit()
    }A
    supportFragmentManager.beginTransaction()
    .replace(android.R.id.content, userFragment)
    t
    .commit()

    View full-size slide

  110. inline fun FragmentManager.transaction(
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    transaction.commit()
    }A
    supportFragmentManager.transaction {
    replace(android.R.id.content, userFragment)
    }Z
    T

    View full-size slide

  111. inline fun FragmentManager.transaction(
    allowStateLoss: Boolean = false
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    if (allowStateLoss) transaction.commitAllowingStateLoss()
    else transaction.commit()
    }A
    supportFragmentManager.transaction {
    replace(android.R.id.content, userFragment)
    }Z

    View full-size slide

  112. inline fun FragmentManager.transaction(
    allowStateLoss: Boolean = false
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    if (allowStateLoss) transaction.commitAllowingStateLoss()
    else transaction.commit()
    }A
    supportFragmentManager.transaction {G
    replace(android.R.id.content, userFragment)
    }Z

    View full-size slide

  113. inline fun FragmentManager.transaction(
    allowStateLoss: Boolean = false,
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    if (allowStateLoss) transaction.commitAllowingStateLoss()
    else transaction.commit()
    }A
    supportFragmentManager.transaction(allowStateLoss = true) {G
    replace(android.R.id.content, userFragment)
    }Z

    View full-size slide

  114. L7 ALOAD 3
    INVOKEVIRTUAL androidx/fragment/app/FragmentManager
    beginTransaction()
    Landroidx/fragment/app/FragmentTransaction;
    ASTORE 7
    L8 ALOAD 7
    LDC 16908290
    ALOAD 2
    INVOKEVIRTUAL androidx/fragment/app/FragmentTransaction
    replace( I Landroidx/fragment/app/Fragment; )
    Landroidx/fragment/app/FragmentTransaction;
    POP
    L9 ALOAD 7
    INVOKEVIRTUAL androidx/fragment/app/FragmentTransaction
    commitAllowingStateLoss()
    I
    POP

    View full-size slide

  115. inline fun FragmentManager.transaction(
    allowStateLoss: Boolean = false,
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    if (allowStateLoss) transaction.commitAllowingStateLoss()
    else transaction.commit()
    }A
    supportFragmentManager.transaction(allowStateLoss = true) {G
    replace(android.R.id.content, userFragment)
    }Z

    View full-size slide

  116. inline fun FragmentManager.transaction(
    now: Boolean = false,
    allowStateLoss: Boolean = false,
    body: FragmentTransaction.() -> Unit
    ) {
    val transaction = beginTransaction()
    transaction.body()
    if (now) {
    if (allowStateLoss) transaction.commitNowAllowingStateLoss()
    else transaction.commitNow()
    } else {
    if (allowStateLoss) transaction.commitAllowingStateLoss()
    else transaction.commit()
    }
    }A

    View full-size slide

  117. Building Kotlin-friendly libraries

    View full-size slide

  118. Building Kotlin-friendly libraries
    • Port public API or entire library to Kotlin

    View full-size slide

  119. Building Kotlin-friendly libraries
    • Port public API or entire library to Kotlin

    • Ship sibling artifact with Kotlin extensions

    View full-size slide

  120. Building Kotlin-friendly libraries
    • Port public API or entire library to Kotlin

    • Ship sibling artifact with Kotlin extensions

    • ???

    View full-size slide

  121. inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  122. class TextUtils {
    static boolean isDigitsOnly(CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  123. class TextUtils {
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  124. class TextUtils {
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  125. class TextUtils {
    @ExtensionFunction
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  126. class TextUtils {
    @ExtensionFunction
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  127. class TextUtils {
    @ExtensionFunction
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  128. class TextUtils {
    @ExtensionFunction
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    val onlyDigits = phoneNumber.isDigitsOnly()

    View full-size slide

  129. class TextUtils {
    @ExtensionFunction
    static boolean isDigitsOnly(@NonNull CharSequence str) {
    int len = str.length();
    // …
    }Y
    }Z
    val onlyDigits = phoneNumber.isDigitsOnly()
    // in the bytecode we get
    val onlyDigits = TextUtils.isDigitsOnly(phoneNumber)

    View full-size slide

  130. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  131. inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  132. class View {
    void setPadding(int left, int top, int right, int bottom) { /* … */ }B
    }A
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  133. class View {
    void setPadding(
    @KtName("left")Aint left,
    @KtName("top")Aint top,
    @KtName("right")Aint right,
    @KtName("bottom")Aint bottom
    ) { /* … */ }B
    }A
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)

    View full-size slide

  134. class View {
    void setPadding(
    @KtName("left")A@DefaultValue("paddingLeft") int left,
    @KtName("top")A@DefaultValue("paddingTop") int top,
    @KtName("right")A@DefaultValue("paddingRight") int right,
    @KtName("bottom")A@DefaultValue("paddingBottom") int bottom
    ) { /* … */ }B
    }A
    inline fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
    ) {
    setPadding(left, top, right, bottom)

    View full-size slide

  135. class View {
    void setPadding(
    @KtName("left") @DefaultValue("paddingLeft") int left,
    @KtName("top") @DefaultValue("paddingTop") int top,
    @KtName("right") @DefaultValue("paddingRight") int right,
    @KtName("bottom") @DefaultValue("paddingBottom") int bottom
    ) { /* … */ }B
    }A
    avatarView.updatePadding(left = 10, right = 10)R

    View full-size slide

  136. class View {
    void setPadding(
    @KtName("left") @DefaultValue("paddingLeft") int left,
    @KtName("top") @DefaultValue("paddingTop") int top,
    @KtName("right") @DefaultValue("paddingRight") int right,
    @KtName("bottom") @DefaultValue("paddingBottom") int bottom
    ) { /* … */ }B
    }A
    avatarView.setPadding(left = 10, right = 10)R
    u
    p
    d
    a
    t
    e

    View full-size slide

  137. class View {
    void setPadding(
    @KtName("left") @DefaultValue("paddingLeft") int left,
    @KtName("top") @DefaultValue("paddingTop") int top,
    @KtName("right") @DefaultValue("paddingRight") int right,
    @KtName("bottom") @DefaultValue("paddingBottom") int bottom
    ) { /* … */ }B
    }A
    avatarView.setPadding(left = 10, right = 10)R
    // in bytecode we get
    avatarView.setPadding(
    10, avatarView.paddingTop, 10, avatarView.paddingBottom)
    u
    p
    d
    a
    t
    e

    View full-size slide

  138. KEEP-110
    • @ExtensionFunction / @ExtensionProperty — Turn a static
    method with at least one argument into an extension function or an
    extension property.

    View full-size slide

  139. KEEP-110
    • @ExtensionFunction / @ExtensionProperty — Turn a static
    method with at least one argument into an extension function or an
    extension property.
    • 


    • @DefaultValue — Default parameter values.

    View full-size slide

  140. KEEP-110
    • @ExtensionFunction / @ExtensionProperty — Turn a static
    method with at least one argument into an extension function or an
    extension property.
    • 


    • @DefaultValue — Default parameter values.
    • 


    • @KtName — An alternate name for methods, fields, and parameters for
    use by Kotlin code.

    View full-size slide

  141. Building Kotlin-friendly libraries
    • Port public API or entire library to Kotlin

    • Ship sibling artifact with Kotlin extensions

    • ???

    View full-size slide

  142. Building Kotlin-friendly libraries
    • Port public API or entire library to Kotlin

    • Ship sibling artifact with Kotlin extensions

    • KEEP-110 annotations

    View full-size slide

  143. Android KTX
    Android framework
    core
    core-ktx
    fragment-ktx fragment
    palette-ktx palette
    collection-ktx collection
    lifecycle-reactivestreams-ktx lifecycle-reactivestreams
    sqlite-ktx sqlite
    navigation-*-ktx navigation-*
    work-runtime-ktx work-runtime

    View full-size slide

  144. https://github.com/Kotlin/KEEP/issues/110

    View full-size slide

  145. Jake Wharton
    Thank you

    @JakeWharton

    View full-size slide