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

  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