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

Getting the most out of Android KTX

Getting the most out of Android KTX

Video: https://www.youtube.com/watch?v=B8ei4rHAHEA

Ever wish you could just use a lambda as an onClickListener? Or change the View visibility by modifying a simple "isVisible" boolean? Or how about using a doOnLayout{} method to run a block of code after view layout?

These are just a few examples of how Android KTX extensions help solve common problems. They improve the existing Jetpack and Android platform APIs so you can consume them from Kotlin in a concise and idiomatic way.

This talk will discuss what KTX extensions are and explore the most useful extensions that you can use in your daily workflow.

Jeroen Mols

April 20, 2020
Tweet

More Decks by Jeroen Mols

Other Decks in Programming

Transcript

  1. @MOLSJEROEN
    GETTING THE MOST
    OUT OF ANDROID KTX

    View Slide

  2. @MOLSJEROEN

    View Slide

  3. @MOLSJEROEN
    WHAT AND WHY
    CRASH COURSE
    WRITE YOUR OWN
    DISCOVER MORE

    View Slide

  4. WHAT AND WHY

    View Slide

  5. @MOLSJEROEN
    ANDROID CAN SOMETIMES BE TEDIOUS
    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) {
    view.removeOnLayoutChangeListener(this)
    doOnWidthAndHeightKnown()
    }
    })

    View Slide

  6. @MOLSJEROEN
    ANDROID CAN SOMETIMES BE TEDIOUS
    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) {
    view.removeOnLayoutChangeListener(this)
    doOnWidthAndHeightKnown()
    }
    })

    View Slide

  7. @MOLSJEROEN
    ANDROID CAN SOMETIMES BE TEDIOUS
    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) {
    view.removeOnLayoutChangeListener(this)
    doOnWidthAndHeightKnown()
    }
    })

    View Slide

  8. @MOLSJEROEN
    ANDROID CAN SOMETIMES BE TEDIOUS
    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) {
    view.removeOnLayoutChangeListener(this)
    doOnWidthAndHeightKnown()
    }
    })

    View Slide

  9. @MOLSJEROEN
    KTX TO THE RESCUE
    view.doOnLayout { doOnWidthAndHeightKnown() }

    View Slide

  10. @MOLSJEROEN
    KTX UNDER THE HOOD
    inline fun View.doOnLayout(
    crossinline action: (view: View) -> Unit
    ) {
    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
    action(this)
    } else {
    doOnNextLayout {
    action(it)
    }
    }
    }

    View Slide

  11. @MOLSJEROEN
    KTX UNDER THE HOOD
    inline fun View.doOnLayout(
    crossinline action: (view: View) -> Unit
    ) {
    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
    action(this)
    } else {
    doOnNextLayout {
    action(it)
    }
    }
    }

    View Slide

  12. @MOLSJEROEN
    KTX UNDER THE HOOD
    inline fun View.doOnLayout(
    crossinline action: (view: View) -> Unit
    ) {
    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
    action(this)
    } else {
    doOnNextLayout {
    action(it)
    }
    }
    }

    View Slide

  13. @MOLSJEROEN
    KTX UNDER THE HOOD
    inline fun View.doOnLayout(
    crossinline action: (view: View) -> Unit
    ) {
    if (ViewCompat.isLaidOut(this) && !isLayoutRequested) {
    action(this)
    } else {
    doOnNextLayout {
    action(it)
    }
    }
    }

    View Slide

  14. @MOLSJEROEN
    KTX UNDER THE HOOD
    inline fun View.doOnNextLayout(
    crossinline action: (view: View) -> Unit
    ) {
    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
    ) {
    view.removeOnLayoutChangeListener(this)
    action(view)
    }
    })
    }

    View Slide

  15. @MOLSJEROEN
    WHAT IS KTX

    View Slide

  16. @MOLSJEROEN
    WHAT IS KTX

    View Slide

  17. @MOLSJEROEN
    WHAT IS KTX

    View Slide

  18. @MOLSJEROEN
    HOW TO USE KTX
    repositories {
    google()
    }
    dependencies {
    implementation "androidx.core:core:1.2.0"
    }

    View Slide

  19. @MOLSJEROEN
    HOW TO USE KTX
    repositories {
    google()
    }
    dependencies {
    implementation "androidx.core:core:1.2.0"
    }

    View Slide

  20. @MOLSJEROEN
    HOW TO USE KTX
    repositories {
    google()
    }
    dependencies {
    implementation “androidx.core:core-ktx:1.2.0”
    }

    View Slide

  21. CRASH COURSE

    View Slide

  22. @MOLSJEROEN
    ANIMATIONS
    animator.addListener(object : Animator.AnimatorListener {
    override fun onAnimationStart(animation: Animator?) =
    doSomething(animation!!)
    override fun onAnimationRepeat(animation: Animator?) =
    doSomething(animation!!)
    override fun onAnimationEnd(animation: Animator?) =
    doSomething(animation!!)
    override fun onAnimationCancel(animation: Animator?) =
    doSomething(animation!!)
    })

    View Slide

  23. @MOLSJEROEN
    ANIMATIONS
    val animator = ObjectAnimator.ofFloat(view, ALPHA, 0f, 1f)
    animator.doOnStart { doSomething(it) }
    animator.doOnRepeat { doSomething(it) }
    animator.doOnCancel { doSomething(it) }
    animator.doOnEnd { doSomething(it) }

    View Slide

  24. @MOLSJEROEN
    ANIMATIONS
    val animator = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f)
    animator.doOnPause { doSomething(it) }
    animator.doOnResume { doSomething(it) }

    View Slide

  25. @MOLSJEROEN
    SYSTEM SERVICES
    // requires android M
    val layoutInflater = context
    .getSystemService(LayoutInflater::class.java)

    View Slide

  26. @MOLSJEROEN
    SYSTEM SERVICES
    // requires android M
    val layoutInflater = context
    .getSystemService(LayoutInflater::class.java)
    // any Android version
    context.getSystemService()

    View Slide

  27. @MOLSJEROEN
    SHAREDPREFERENCES
    sharedPrefs.edit().putBoolean("key", true).apply()

    View Slide

  28. @MOLSJEROEN
    SHAREDPREFERENCES
    sharedPrefs.edit().putBoolean("key", true).apply()
    sharedPrefs.edit { putBoolean("key", true) }
    sharedPrefs.edit(commit = true) { putBoolean("key", true) }

    View Slide

  29. @MOLSJEROEN
    COLORS
    val red = Color.parseColor("#ff0000")
    val colorDrawable = ColorDrawable(red)

    View Slide

  30. @MOLSJEROEN
    COLORS
    val red = Color.parseColor("#ff0000")
    val colorDrawable = ColorDrawable(red)
    val red = "#ff0000".toColorInt()
    val colorDrawable = red.toDrawable()

    View Slide

  31. @MOLSJEROEN
    URI AND FILE
    val uri = “https://jeroenmols.com/blog”.toUri()
    val file = uri.toFile()
    val fileUri = file.toUri()

    View Slide

  32. @MOLSJEROEN
    VIEW
    view.doOnLayout { }
    view.doOnNextLayout { }
    view.doOnPreDraw { }
    view.doOnAttach { }
    view.doOnDetach { }
    view.marginStart
    view.marginEnd
    view.marginLeft
    view.marginRight
    view.marginTop
    view.marginBottom

    View Slide

  33. @MOLSJEROEN
    VIEW
    view.updateLayoutParams {
    width = 200 // strong typed access
    height = 200
    }
    view.updatePadding(right = 10) // only assign one
    val bitmap = view.drawToBitmap()

    View Slide

  34. @MOLSJEROEN
    VIEW VISIBILITY
    if (view.visibility == View.VISIBLE) {
    // do something
    }
    view.visibility = View.VISIBLE

    View Slide

  35. @MOLSJEROEN
    VIEW VISIBILITY
    if (view.visibility == View.VISIBLE) {
    // do something
    }
    view.visibility = View.VISIBLE
    if (view.isVisible) {
    // do something
    }
    view.isVisible = false // View.GONE

    View Slide

  36. @MOLSJEROEN
    VIEWGROUP
    viewGroup.contains(view)
    viewGroup.forEach { }
    viewGroup.isEmpty()
    viewGroup.isNotEmpty()
    viewGroup += view
    viewGroup -= view

    View Slide

  37. @MOLSJEROEN
    TEXTVIEW
    editText.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) =
    doWork()
    override fun beforeTextChanged(s: CharSequence?, start: Int,…) =
    doWork()
    override fun onTextChanged(s: CharSequence?, start: Int,…) =
    doWork()
    })

    View Slide

  38. @MOLSJEROEN
    TEXTVIEW
    editText.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) =
    doWork()
    override fun beforeTextChanged(s: CharSequence?, start: Int,…) =
    doWork()
    override fun onTextChanged(s: CharSequence?, start: Int,…) =
    doWork()
    })
    editText.doOnTextChanged { text, start, count, after -> doWork() }
    editText.doAfterTextChanged { doWork() }
    editText.doBeforeTextChanged { text, start, count, after -> doWork() }

    View Slide

  39. @MOLSJEROEN
    VIEWMODELS - ACTIVITY
    // scoped to activity
    val viewmodel = ViewModelProviders.of(this)
    .get(MyViewModel::class.java)

    View Slide

  40. @MOLSJEROEN
    VIEWMODELS - ACTIVITY
    // scoped to activity
    val viewmodel = ViewModelProviders.of(this)
    .get(MyViewModel::class.java)
    // scoped to activity
    val viewModel by viewModels()

    View Slide

  41. @MOLSJEROEN
    VIEWMODELS - FRAGMENT
    // scoped to fragment
    val viewModel = ViewModelProviders.of(this)
    .get(MyViewModel::class.java)
    // scoped to activity
    val viewModel = ViewModelProviders.of(requireActivity())
    .get(MyViewModel::class.java)

    View Slide

  42. @MOLSJEROEN
    VIEWMODELS - FRAGMENT
    // scoped to fragment
    val viewModel = ViewModelProviders.of(this)
    .get(MyViewModel::class.java)
    // scoped to activity
    val viewModel = ViewModelProviders.of(requireActivity())
    .get(MyViewModel::class.java)
    // scoped to fragment
    val viewModel by viewModels()
    // scoped to activity
    val activityViewModel by activityViewModels()

    View Slide

  43. @MOLSJEROEN
    VIEWMODELS - FACTORY
    val viewModel = ViewModelProviders.of(this, VModelFactory())
    .get(MyViewModel::class.java)

    View Slide

  44. @MOLSJEROEN
    VIEWMODELS - FACTORY
    val viewModel = ViewModelProviders.of(this, VModelFactory())
    .get(MyViewModel::class.java)
    val viewModel by viewModels() { VModelFactory() }

    View Slide

  45. @MOLSJEROEN
    FRAGMENT TRANSACTIONS
    fragmentManager.beginTransaction()
    .replace(R.id.container, Fragment()).commit()

    View Slide

  46. @MOLSJEROEN
    FRAGMENT TRANSACTIONS
    fragmentManager.beginTransaction()
    .replace(R.id.container, Fragment()).commit()
    fragmentManager.commit { replace(R.id.container, Fragment()) }
    fragmentManager.commit(allowStateLoss = true)
    { replace(R.id.container, Fragment()) }

    View Slide

  47. @MOLSJEROEN
    COROUTINE SCOPES
    AppCompatActivity().lifecycleScope.launch { }
    Fragment().lifecycleScope.launch { }
    ViewModel().viewModelScope.launch { }

    View Slide

  48. @MOLSJEROEN
    AND MANY MORE
    Benchmark
    Graphics
    Spannable
    SparseArrays
    Ranges
    Menu
    Lifecycle
    Livedata & flow
    Navigation
    Paging
    Preferences
    Room
    Slices
    Sqlite
    Workmanager
    Playcore

    bit.ly/ktx-extensions

    View Slide

  49. WRITE YOUR OWN

    View Slide

  50. @MOLSJEROEN
    LIMIT DECIMALS IN EDITTEXT
    fun EditText.limitDecimalsTo(numDecimals: Int) {
    filters += InputFilter { s, start, end, d, dstart, dend ->
    val newText = s.subSequence(start, end).toString()
    val result = StringBuilder(d)
    .replace(dstart, dend, newText).toString()
    val regex = “[0-9]*((\\.[0-9]{0,$numDecimals})?)||(\\.)?"
    .toRegex()
    if (regex.matches(result)) null else ""
    }
    }
    editText.limitDecimalsTo(2)

    View Slide

  51. @MOLSJEROEN
    LIMIT DECIMALS IN EDITTEXT
    fun EditText.limitDecimalsTo(numDecimals: Int) {
    filters += InputFilter { s, start, end, d, dstart, dend ->
    val newText = s.subSequence(start, end).toString()
    val result = StringBuilder(d)
    .replace(dstart, dend, newText).toString()
    val regex = “[0-9]*((\\.[0-9]{0,$numDecimals})?)||(\\.)?"
    .toRegex()
    if (regex.matches(result)) null else ""
    }
    }
    editText.limitDecimalsTo(2)

    View Slide

  52. @MOLSJEROEN
    LIMIT DECIMALS IN EDITTEXT
    fun EditText.limitDecimalsTo(numDecimals: Int) {
    filters += InputFilter { s, start, end, d, dstart, dend ->
    val newText = s.subSequence(start, end).toString()
    val result = StringBuilder(d)
    .replace(dstart, dend, newText).toString()
    val regex = “[0-9]*((\\.[0-9]{0,$numDecimals})?)||(\\.)?"
    .toRegex()
    if (regex.matches(result)) null else ""
    }
    }
    editText.limitDecimalsTo(2)

    View Slide

  53. @MOLSJEROEN
    TOAST
    fun Fragment.toast(@StringRes message: Int) =
    Toast.makeText(context, message, LENGTH_SHORT).show()
    fun Fragment.toast(message: String) =
    Toast.makeText(context, message, LENGTH_SHORT).show()
    toast(R.id.mymessage)
    toast("Hello there")

    View Slide

  54. @MOLSJEROEN
    TOAST
    fun Fragment.toast(@StringRes message: Int) =
    Toast.makeText(context, message, LENGTH_SHORT).show()
    fun Fragment.toast(message: String) =
    Toast.makeText(context, message, LENGTH_SHORT).show()
    toast(R.id.mymessage)
    toast("Hello there")

    View Slide

  55. @MOLSJEROEN
    FLOAT
    fun Float.isDecimalNumber() = this % 1 > 0.0001
    fun Float.decimalsOrRound(digits: Int) =
    if (isDecimalNumber()) {
    String.format("%.${digits}f", this)
    } else {
    String.format("%.0f", this)
    }
    12.3f.isDecimalNumber()
    12.3001f.decimalsOrRound(2)

    View Slide

  56. @MOLSJEROEN
    FLOAT
    fun Float.isDecimalNumber() = this % 1 > 0.0001
    fun Float.decimalsOrRound(digits: Int) =
    if (isDecimalNumber()) {
    String.format("%.${digits}f", this)
    } else {
    String.format("%.0f", this)
    }
    12.3f.isDecimalNumber()
    12.3001f.decimalsOrRound(2)

    View Slide

  57. @MOLSJEROEN
    TABSELECTED LISTENER
    fun TabLayout.doOnTabSelected(listener: (text: String) -> Unit) {
    clearOnTabSelectedListeners()
    addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
    override fun onTabReselected(tab: TabLayout.Tab?) = Unit
    override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
    override fun onTabSelected(tab: TabLayout.Tab?) =
    listener.invoke(tab!!.text.toString())
    })
    }
    tabLayout.doOnTabSelected { doSomething() }

    View Slide

  58. @MOLSJEROEN
    TABSELECTED LISTENER
    fun TabLayout.doOnTabSelected(listener: (text: String) -> Unit) {
    clearOnTabSelectedListeners()
    addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
    override fun onTabReselected(tab: TabLayout.Tab?) = Unit
    override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
    override fun onTabSelected(tab: TabLayout.Tab?) =
    listener.invoke(tab!!.text.toString())
    })
    }
    tabLayout.doOnTabSelected { doSomething() }

    View Slide

  59. DISCOVER MORE

    View Slide

  60. @MOLSJEROEN
    LEARN MORE ABOUT KTX
    bit.ly/ktx-docs
    bit.ly/ktx-extensions
    bit.ly/ktx-source

    View Slide

  61. WRAP UP

    View Slide

  62. @MOLSJEROEN
    IDIOMATIC CODE
    SIMPLIFY DEVELOPMENT
    REDUCE ERRORS
    WRITE YOUR OWN
    GET INSPIRED

    View Slide

  63. @MOLSJEROEN
    HTTPS://JEROENMOLS.COM/BLOG

    View Slide

  64. @MOLSJEROEN
    IMAGE CREDITS
    Welcome image by Clement127
    https://flic.kr/p/QdpUY8
    Official Android and Google logo's
    Font awesome
    https://fontawesome.com/

    View Slide

  65. MOLSJEROEN

    View Slide