Pro Yearly is on sale from $80 to $50! »

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.

5f57d2d205e77e185986459c1b89a874?s=128

Jeroen Mols

April 20, 2020
Tweet

Transcript

  1. @MOLSJEROEN GETTING THE MOST OUT OF ANDROID KTX

  2. @MOLSJEROEN

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

    MORE
  4. WHAT AND WHY

  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() } })
  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() } })
  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() } })
  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() } })
  9. @MOLSJEROEN KTX TO THE RESCUE view.doOnLayout { doOnWidthAndHeightKnown() }

  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) } } }
  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) } } }
  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) } } }
  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) } } }
  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) } }) }
  15. @MOLSJEROEN WHAT IS KTX

  16. @MOLSJEROEN WHAT IS KTX

  17. @MOLSJEROEN WHAT IS KTX

  18. @MOLSJEROEN HOW TO USE KTX repositories { google() } dependencies

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

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

    { implementation “androidx.core:core-ktx:1.2.0” }
  21. CRASH COURSE

  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!!) })
  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) }
  24. @MOLSJEROEN ANIMATIONS val animator = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f) animator.doOnPause

    { doSomething(it) } animator.doOnResume { doSomething(it) }
  25. @MOLSJEROEN SYSTEM SERVICES // requires android M val layoutInflater =

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

    context .getSystemService(LayoutInflater::class.java) // any Android version context.getSystemService<LayoutInflater>()
  27. @MOLSJEROEN SHAREDPREFERENCES sharedPrefs.edit().putBoolean("key", true).apply()

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

    = true) { putBoolean("key", true) }
  29. @MOLSJEROEN COLORS val red = Color.parseColor("#ff0000") val colorDrawable = ColorDrawable(red)

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

    val red = "#ff0000".toColorInt() val colorDrawable = red.toDrawable()
  31. @MOLSJEROEN URI AND FILE val uri = “https://jeroenmols.com/blog”.toUri() val file

    = uri.toFile() val fileUri = file.toUri()
  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
  33. @MOLSJEROEN VIEW view.updateLayoutParams<ConstraintLayout.LayoutParams> { width = 200 // strong typed

    access height = 200 } view.updatePadding(right = 10) // only assign one val bitmap = view.drawToBitmap()
  34. @MOLSJEROEN VIEW VISIBILITY if (view.visibility == View.VISIBLE) { // do

    something } view.visibility = View.VISIBLE
  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
  36. @MOLSJEROEN VIEWGROUP viewGroup.contains(view) viewGroup.forEach { } viewGroup.isEmpty() viewGroup.isNotEmpty() viewGroup +=

    view viewGroup -= view
  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() })
  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() }
  39. @MOLSJEROEN VIEWMODELS - ACTIVITY // scoped to activity val viewmodel

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

    = ViewModelProviders.of(this) .get(MyViewModel::class.java) // scoped to activity val viewModel by viewModels<MyViewModel>()
  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)
  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<MyViewModel>() // scoped to activity val activityViewModel by activityViewModels<MyViewModel>()
  43. @MOLSJEROEN VIEWMODELS - FACTORY val viewModel = ViewModelProviders.of(this, VModelFactory()) .get(MyViewModel::class.java)

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

    val viewModel by viewModels<MyViewModel>() { VModelFactory() }
  45. @MOLSJEROEN FRAGMENT TRANSACTIONS fragmentManager.beginTransaction() .replace(R.id.container, Fragment()).commit()

  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()) }
  47. @MOLSJEROEN COROUTINE SCOPES AppCompatActivity().lifecycleScope.launch { } Fragment().lifecycleScope.launch { } ViewModel().viewModelScope.launch

    { }
  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
  49. WRITE YOUR OWN

  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)
  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)
  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)
  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")
  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")
  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)
  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)
  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() }
  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() }
  59. DISCOVER MORE

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

  61. WRAP UP

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

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

  64. @MOLSJEROEN IMAGE CREDITS Welcome image by Clement127 https://flic.kr/p/QdpUY8 Official Android

    and Google logo's Font awesome https://fontawesome.com/
  65. MOLSJEROEN