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

Android KTX: A dash of Kotlin makes all the difference! (KotlinConf 2018)

Dan Kim
October 05, 2018

Android KTX: A dash of Kotlin makes all the difference! (KotlinConf 2018)

When Kotlin hit the Android scene, one of the first things many of us did was wrap difficult-to-use Android Framework APIs with extension functions. That little sprinkle of Kotlin made all the difference, and life was great — we finally we had a way to make our least favorite calls more concise and easier to work with!

Android KTX builds on this same idea and makes them available to everyone. It's a new set of open source Kotlin extensions (maintained by Google) that sits on top of commonly used Framework methods, abstracting much of the complexity and boilerplate away. This vastly improves the conciseness and readability of your code, making it more idiomatic and enjoyable to work with.

In this talk we'll start with a brief intro to KTX — what it is, why it's so great, and what it means for us as Android developers. Next we'll take a tour of what extensions are currently available, highlighting some of my favorites. We'll compare the old, hard ways of doing things against the new, friendlier ways that KTX provides so you can clearly see how life with KTX is better!

By the end you’ll have a better idea of 1) how you can improve your Android code by using KTX in your daily work and 2) what well-designed extension methods look like so you can wrap/extend the Android framework for your own use (or to contribute back into KTX!)

Dan Kim

October 05, 2018
Tweet

More Decks by Dan Kim

Other Decks in Programming

Transcript

  1. view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( view: View?, left:

    Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int ) { doSomething() } })
  2. view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( view: View?, left:

    Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int ) { doSomething() } })
  3. // Original view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( view:

    View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int ) { ... } }) // KTX view.doOnNextLayout { doSomething() }
  4. // Implementation fun Drawable.updateBounds(@Px left: Int = bounds.left, @Px top:

    Int = bounds.top, @Px right: Int = bounds.right, @Px bottom: Int = bounds.bottom) {xx setBounds(left, top, right, bottom) }xx
  5. // Original animator.addListener(object : Animator.AnimatorListener { override fun onAnimationEnd(a: Animator?)

    { ... } override fun onAnimationStart(a: Animator?) { } override fun onAnimationCancel(a: Animator?) { } override fun onAnimationRepeat(a: Animator?) { } })x
  6. // Original animator.addListener(object : Animator.AnimatorListener { override fun onAnimationEnd(a: Animator?)

    { ... } override fun onAnimationStart(a: Animator?) { } override fun onAnimationCancel(a: Animator?) { } override fun onAnimationRepeat(a: Animator?) { } })x // KTX animator.addListener(onEnd = { ... })
  7. // Original animator.addListener(object : Animator.AnimatorListener { override fun onAnimationEnd(a: Animator?)

    { ... } override fun onAnimationStart(a: Animator?) { } override fun onAnimationCancel(a: Animator?) { } override fun onAnimationRepeat(a: Animator?) { } }) // KTX animator.addListener(onEnd = { ... }) animator.doOnEnd { ... }
  8. // Implementation inline fun Animator.addListener( crossinline onEnd: (a: Animator) ->

    Unit = {}, crossinline onStart: (a: Animator) -> Unit = {}, crossinline onCancel: (a: Animator) -> Unit = {}, crossinline onRepeat: (a: Animator) -> Unit = {} ): Animator.AnimatorListener {x ... }x
  9. transition.addListener(object : Transition.TransitionListener { override fun onTransitionEnd(t: Transition?) { ...

    } override fun onTransitionStart(t: Transition?) { } override fun onTransitionCancel(t: Transition) { } override fun onTransitionResume(t: Transition?) { } override fun onTransitionPause(t: Transition?) { } })x
  10. // Original transition.addListener(object : Transition.TransitionListener { override fun onTransitionEnd(t: Transition?)

    { ... } override fun onTransitionStart(t: Transition?) { } override fun onTransitionCancel(t: Transition) { } override fun onTransitionResume(t: Transition?) { } override fun onTransitionPause(t: Transition?) { } })x // KTX transition.addListener(onEnd = { ... })
  11. // Original transition.addListener(object : Transition.TransitionListener { override fun onTransitionEnd(t: Transition?)

    { ... } override fun onTransitionStart(t: Transition?) { } override fun onTransitionCancel(t: Transition) { } override fun onTransitionResume(t: Transition?) { } override fun onTransitionPause(t: Transition?) { } }) // KTX transition.addListener(onEnd = { ... }) transition.doOnEnd { ... }
  12. a++ a-- a + b a += b a *

    b a / b a % b a..b a in b a[i] a[i] = b ...
  13. // Implementation inline operator fun Bitmap.set( x: Int, y: Int,

    @ColorInt color: Int) = setPixel(x, y, color)
  14. // Implementation inline operator fun Bitmap.set( x: Int, y: Int,

    @ColorInt color: Int) = setPixel(x, y, color)
  15. val left = rect.left val top = rect.top val right

    = rect.right val bottom = rect.bottom
  16. // Original val left = rect.left val top = rect.top

    val right = rect.right val bottom = rect.bottom // KTX val (left, top, right, bottom) = rect
  17. inline operator fun Rect.component1() = this.left inline operator fun Rect.component2()

    = this.top inline operator fun Rect.component3() = this.right inline operator fun Rect.component4() = this.bottom
  18. inline operator fun Rect.component1() = this.left inline operator fun Rect.component2()

    = this.top inline operator fun Rect.component3() = this.right inline operator fun Rect.component4() = this.bottom
  19. val red = colorInt.red val green = colorInt.green val blue

    = colorInt.blue val alpha = colorInt.alpha
  20. // Original val red = colorInt.red val green = colorInt.green

    val blue = colorInt.blue val alpha = colorInt.alpha // KTX val (red, green, blue, alpha) = colorInt
  21. // Implementation fun @receiver:ColorInt Int.component1() = (this shr 24) and

    0xff fun @receiver:ColorInt Int.component2() = (this shr 16) and 0xff fun @receiver:ColorInt Int.component3() = (this shr 8) and 0xff fun @receiver:ColorInt Int.component4() = this and 0xffx
  22. // Implementation fun @receiver:ColorInt Int.component1() = (this shr 24) and

    0xff fun @receiver:ColorInt Int.component2() = (this shr 16) and 0xff fun @receiver:ColorInt Int.component3() = (this shr 8) and 0xff fun @receiver:ColorInt Int.component4() = this and 0xffx
  23. // Implementation inline fun ViewGroup.forEach(action: (view: View) -> Unit) {

    for (index in 0 until childCount) { action(getChildAt(index)) } x } x
  24. // Implementation inline fun ViewGroup.forEach(action: (view: View) -> Unit) {

    for (index in 0 until childCount) { action(getChildAt(index)) } x } x
  25. viewGroup.contains(view.textView) viewGroup.forEach { ... } viewGroup.forEachIndexed { index, view ->

    ... } viewGroup.size viewGroup.isEmpty() viewGroup.isNotEmpty() val view = viewGroup[0]
  26. for (index in 0 until menu.size()) { if (menu.getItem(index) ==

    menuItem) { return true } x } x return false
  27. // Original for (index in 0 until menu.size()) { if

    (menu.getItem(index) == menuItem) { return true } x } x return false // KTX menu.contains(menuItem)
  28. // Implementation operator fun Menu.contains(item: MenuItem): Boolean { for (index

    in 0 until size()) { if (getItem(index) == item) { return true } x } x return false } x
  29. // Implementation operator fun Menu.contains(item: MenuItem): Boolean { for (index

    in 0 until size()) { if (getItem(index) == item) { return true } x } x return false } x
  30. // Implementation inline fun createBitmap( width: Int, height: Int, config:

    Bitmap.Config = Bitmap.Config.ARGB_8888, hasAlpha: Boolean = true, colorSpace: ColorSpace = ColorSpace.get(ColorSpace.Named.SRGB) ): Bitmap { return Bitmap.createBitmap(width, height, config, hasAlpha, colorSpace) }x
  31. // Implementation inline fun createBitmap( width: Int, height: Int, config:

    Bitmap.Config = Bitmap.Config.ARGB_8888, hasAlpha: Boolean = true, colorSpace: ColorSpace = ColorSpace.get(ColorSpace.Named.SRGB) ): Bitmap { return Bitmap.createBitmap(width, height, config, hasAlpha, colorSpace) }x
  32. val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap)

    canvas.translate(-view.scrollX.toFloat(), -view.scrollY.toFloat()) view.draw(canvas)
  33. // Original val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) val canvas

    = Canvas(bitmap) canvas.translate(-view.scrollX.toFloat(), -view.scrollY.toFloat()) view.draw(canvas) // KTX view.drawToBitmap()
  34. // Original sharedPrefs.edit() .putBoolean( key, value x ) .apply() //

    KTX sharedPrefs.edit { putBoolean(key, value) }
  35. val builder = SpannableStringBuilder() val start = builder.length builder.append("Hello") builder.setSpan(

    StyleSpan(Typeface.BOLD), start, builder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE )x
  36. // Original val builder = SpannableStringBuilder() val start = builder.length

    builder.append("Hello") builder.setSpan( StyleSpan(Typeface.BOLD), start, builder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE )x // KTX builder.bold { append("Hello") }
  37. // Original val builder = SpannableStringBuilder() val start = builder.length

    builder.append("Hello") builder.setSpan( StyleSpan(Typeface.BOLD), start, builder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE )x // KTX builder.bold { append("Hello") } builder.bold { italic { underline { append("Hello") } } }
  38. // Implementation fun bundleOf(vararg pairs: Pair<String, Any?>) = Bundle(pairs.size).apply {

    for ((key, value) in pairs) { when (value) { null -> putString(key, null) // Any nullable type will suffice. // Scalars is Boolean -> putBoolean(key, value) is Byte -> putByte(key, value) is Char -> putChar(key, value) is Double -> putDouble(key, value) is Float -> putFloat(key, value) is Int -> putInt(key, value) is Long -> putLong(key, value) is Short -> putShort(key, value) // References is Bundle -> putBundle(key, value) is CharSequence -> putCharSequence(key, value) is Parcelable -> putParcelable(key, value) // Scalar arrays is BooleanArray -> putBooleanArray(key, value) is ByteArray -> putByteArray(key, value) is CharArray -> putCharArray(key, value) is DoubleArray -> putDoubleArray(key, value) is FloatArray -> putFloatArray(key, value) is IntArray -> putIntArray(key, value) is LongArray -> putLongArray(key, value) is ShortArray -> putShortArray(key, value) // Reference arrays is Array<*> -> { val componentType = value::class.java.componentType @Suppress("UNCHECKED_CAST") // Checked by reflection. when { Parcelable::class.java.isAssignableFrom(componentType) -> { putParcelableArray(key, value as Array<Parcelable>) } String::class.java.isAssignableFrom(componentType) -> { putStringArray(key, value as Array<String>) } CharSequence::class.java.isAssignableFrom(componentType) -> { putCharSequenceArray(key, value as Array<CharSequence>)