Slide 1

Slide 1 text

Android KTX A dash of Kotlin makes all the difference! @dankim

Slide 2

Slide 2 text

Android KTX A dash of Kotlin makes all the difference! @dankim

Slide 3

Slide 3 text

What is KTX? (besides just a cool name)

Slide 4

Slide 4 text

Kotlin extensions designed specifically for Android

Slide 5

Slide 5 text

So why should I care?

Slide 6

Slide 6 text

Makes Android development more concise, pleasant, and idiomatic®

Slide 7

Slide 7 text

Writing clean, readable code is a lot easier and a lot more fun

Slide 8

Slide 8 text

Android has a long, colorful history Android has a long, colorful history

Slide 9

Slide 9 text

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() } })

Slide 10

Slide 10 text

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() } })

Slide 11

Slide 11 text

// 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() }

Slide 12

Slide 12 text

You can learn a lot

Slide 13

Slide 13 text

Are we adding any new functionality?

Slide 14

Slide 14 text

Are we leveraging Kotlin-specific language features?

Slide 15

Slide 15 text

Will the extension be useful in the long-term?

Slide 16

Slide 16 text

Does the extension provide a meaningful upgrade?

Slide 17

Slide 17 text

Is the intent of the extension clear?

Slide 18

Slide 18 text

Enough talk, more code!

Slide 19

Slide 19 text

repositories { google() } dependencies { implementation 'androidx.core:core-ktx:1.0.0' }

Slide 20

Slide 20 text

androidx.core:core-ktx androidx.fragment:fragment-ktx androidx.palette:palette-ktx androidx.sqlite:sqlite-ktx androidx.collection:collection-ktx androidx.lifecycle:lifecycle-viewmodel-ktx androidx.lifecycle:lifecycle-reactivestreams-ktx android.arch.navigation:navigation-common-ktx android.arch.navigation:navigation-fragment-ktx android.arch.navigation:navigation-runtime-ktx android.arch.navigation:navigation-testing-ktx android.arch.navigation:navigation-ui-ktx android.arch.work:work-runtime-ktx

Slide 21

Slide 21 text

androidx.core:core-ktx

Slide 22

Slide 22 text

androidx.core.animation androidx.core.content androidx.core.graphics androidx.core.graphics.drawable androidx.core.net androidx.core.os androidx.core.preference androidx.core.text androidx.core.transition androidx.core.util androidx.core.view androidx.core.widget

Slide 23

Slide 23 text

A note about examples

Slide 24

Slide 24 text

Named parameters & default values

Slide 25

Slide 25 text

drawable.setBounds(drawable.bounds.left, drawable.bounds.top, drawable.bounds.right, 100)

Slide 26

Slide 26 text

// Original drawable.setBounds(drawable.bounds.left, drawable.bounds.top, drawable.bounds.right, 100) // KTX drawable.updateBounds(bottom = 100)

Slide 27

Slide 27 text

// 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

Slide 28

Slide 28 text

// 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

Slide 29

Slide 29 text

// 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 = { ... })

Slide 30

Slide 30 text

// 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 { ... }

Slide 31

Slide 31 text

// Implementation fun Animator.doOnEnd(crossinline action: (a: Animator) -> Unit) = addListener(onEnd = action)

Slide 32

Slide 32 text

// Implementation fun Animator.doOnEnd(crossinline action: (a: Animator) -> Unit) = addListener(onEnd = action)

Slide 33

Slide 33 text

// 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

Slide 34

Slide 34 text

animator.doOnEnd { } animator.doOnCancel { } animator.doOnPause { } animator.doOnRepeat { } animator.doOnResume { } animator.doOnStart { }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

// 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 = { ... })

Slide 37

Slide 37 text

// 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 { ... }

Slide 38

Slide 38 text

Operator Overloading

Slide 39

Slide 39 text

a++ a-- a + b a += b a * b a / b a % b a..b a in b a[i] a[i] = b ...

Slide 40

Slide 40 text

bitmap.getPixel(100, 100)

Slide 41

Slide 41 text

// Original bitmap.getPixel(100, 100) // KTX bitmap[100, 100]

Slide 42

Slide 42 text

// Implementation inline operator fun Bitmap.get(x: Int, y: Int) = getPixel(x, y)

Slide 43

Slide 43 text

// Implementation inline operator fun Bitmap.get(x: Int, y: Int) = ...

Slide 44

Slide 44 text

bitmap.setPixel(100, 100, colorInt)

Slide 45

Slide 45 text

// Original bitmap.setPixel(100, 100, colorInt) // KTX bitmap[100, 100] = colorInt

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

rect.union(rect2)

Slide 49

Slide 49 text

// Original rect.union(rect2) // KTX rect + rect2

Slide 50

Slide 50 text

// Implementation inline operator fun Rect.plus(r: Rect): Rect { return Rect(this).apply { union(r) }xx }xx

Slide 51

Slide 51 text

// Implementation inline operator fun Rect.plus(r: Rect): Rect { return Rect(this).apply { union(r) }x }x

Slide 52

Slide 52 text

menu.removeItem(menuItem.itemId)

Slide 53

Slide 53 text

// Original menu.removeItem(menuItem.itemId) // KTX menu -= menuItem

Slide 54

Slide 54 text

// Implementation inline operator fun Menu.minusAssign(item: MenuItem) = removeItem(item.itemId)

Slide 55

Slide 55 text

// Implementation inline operator fun Menu. minusAssign(item: MenuItem) = removeItem(item.itemId)

Slide 56

Slide 56 text

Destructuring

Slide 57

Slide 57 text

val lat = location.latitude val long = location.longitude

Slide 58

Slide 58 text

// Original val lat = location.latitude val long = location.longitude // KTX val (lat, long) = location

Slide 59

Slide 59 text

// Implementation inline operator fun Location.component1() = this.latitude inline operator fun Location.component2() = this.longitude

Slide 60

Slide 60 text

// Implementation inline operator fun Location.component1() = this.latitude inline operator fun Location.component2() = this.longitude

Slide 61

Slide 61 text

val left = rect.left val top = rect.top val right = rect.right val bottom = rect.bottom

Slide 62

Slide 62 text

// Original val left = rect.left val top = rect.top val right = rect.right val bottom = rect.bottom // KTX val (left, top, right, bottom) = rect

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

val red = colorInt.red val green = colorInt.green val blue = colorInt.blue val alpha = colorInt.alpha

Slide 66

Slide 66 text

// Original val red = colorInt.red val green = colorInt.green val blue = colorInt.blue val alpha = colorInt.alpha // KTX val (red, green, blue, alpha) = colorInt

Slide 67

Slide 67 text

// 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

Slide 68

Slide 68 text

// 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

Slide 69

Slide 69 text

Pseudo Collections

Slide 70

Slide 70 text

viewGroup.childCount != 0

Slide 71

Slide 71 text

// Original viewGroup.childCount != 0 // KTX viewGroup.isEmpty() viewGroup.isNotEmpty()

Slide 72

Slide 72 text

// Implementation inline fun ViewGroup.isEmpty() = childCount == 0 inline fun ViewGroup.isNotEmpty() = childCount != 0

Slide 73

Slide 73 text

for (index in 0 until viewGroup.childCount) { doSomething(viewGroup.getChildAt(index)) } x

Slide 74

Slide 74 text

// Original for (index in 0 until viewGroup.childCount) { doSomething(viewGroup.getChildAt(index)) } x // KTX viewGroup.forEach { doSomething() }

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

viewGroup.indexOfChild(view) != -1

Slide 78

Slide 78 text

// Original viewGroup.indexOfChild(view) != -1 // KTX viewGroup.contains(view)

Slide 79

Slide 79 text

// Implementation inline operator fun ViewGroup.contains(view: View) = indexOfChild(view) != -1

Slide 80

Slide 80 text

// Implementation inline operator fun ViewGroup.contains(view: View) = indexOfChild(view) != -1

Slide 81

Slide 81 text

viewGroup.contains(view.textView) viewGroup.forEach { ... } viewGroup.forEachIndexed { index, view -> ... } viewGroup.size viewGroup.isEmpty() viewGroup.isNotEmpty() val view = viewGroup[0]

Slide 82

Slide 82 text

for (index in 0 until menu.size()) { if (menu.getItem(index) == menuItem) { return true } x } x return false

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

// 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

Slide 85

Slide 85 text

// 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

Slide 86

Slide 86 text

menu.contains(menuItem) menu.size menu.isEmpty() menu.isNotEmpty() menu.forEach { ... } menu.forEachIndexed { i, item -> ... }

Slide 87

Slide 87 text

Making Life Easier / Discoverability

Slide 88

Slide 88 text

Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888, true, ColorSpace.get(ColorSpace.Named.SRGB))

Slide 89

Slide 89 text

// Original Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888, true, ColorSpace.get(ColorSpace.Named.SRGB)) // KTX createBitmap(100, 100)

Slide 90

Slide 90 text

// 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

Slide 91

Slide 91 text

// 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

Slide 92

Slide 92 text

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)

Slide 93

Slide 93 text

// 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()

Slide 94

Slide 94 text

sharedPrefs.edit() .putBoolean(key, value) .apply()

Slide 95

Slide 95 text

// Original sharedPrefs.edit() .putBoolean( key, value x ) .apply() // KTX sharedPrefs.edit { putBoolean(key, value) }

Slide 96

Slide 96 text

val builder = SpannableStringBuilder() val start = builder.length builder.append("Hello") builder.setSpan( StyleSpan(Typeface.BOLD), start, builder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE )x

Slide 97

Slide 97 text

// 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") }

Slide 98

Slide 98 text

// 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") } } }

Slide 99

Slide 99 text

bundle.putString("Hello", “KotlinConf") bundle.putInt((“Kotlin"), 100) bundle.putBoolean("Java", false)x

Slide 100

Slide 100 text

// Original bundle.putString("Hello", "KotlinConf") bundle.putInt(("Kotlin"), 100) bundle.putBoolean("Java", false)x // KTX bundleOf("Hello" to "KotlinConf", "Kotlin" to 100, "Java" to true)

Slide 101

Slide 101 text

// Implementation fun bundleOf(vararg pairs: Pair) = 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) } String::class.java.isAssignableFrom(componentType) -> { putStringArray(key, value as Array) } CharSequence::class.java.isAssignableFrom(componentType) -> { putCharSequenceArray(key, value as Array)

Slide 102

Slide 102 text

db.beginTransaction() try { ...x db.setTransactionSuccessful() } finally { db.endTransaction() }x

Slide 103

Slide 103 text

// Original db.beginTransaction() try { ...x db.setTransactionSuccessful() } finally { db.endTransaction() }x // KTX db.transaction { ... }

Slide 104

Slide 104 text

ContextCompat.getSystemService(this, AlarmManager::class.java)

Slide 105

Slide 105 text

// Original ContextCompat.getSystemService(this, AlarmManager::class.java) // KTX getSystemService()

Slide 106

Slide 106 text

view.postDelayed({ ... }, 200)

Slide 107

Slide 107 text

// Original view.postDelayed({ ... }, 200) // KTX view.postDelayed(200) { ... }

Slide 108

Slide 108 text

Things to keep in mind

Slide 109

Slide 109 text

Conflicts with your own extensions?

Slide 110

Slide 110 text

What’s good for core-ktx is not a value judgement on all extensions

Slide 111

Slide 111 text

Should I still use Anko & Kotlin Android Extensions?

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

Contributing to KTX

Slide 114

Slide 114 text

AOSP

Slide 115

Slide 115 text

They're "just" extensions

Slide 116

Slide 116 text

Low barrier to entry

Slide 117

Slide 117 text

Don't be discouraged by "no"

Slide 118

Slide 118 text

Start with discussion

Slide 119

Slide 119 text

AOSP: https://android.googlesource.com/platform/frameworks/support/ Issue tracker: https://issuetracker.google.com/issues?q=componentid: 396204%20status:open

Slide 120

Slide 120 text

Thank you! @dankim