Share
Embed
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
Transcript
65 slides
-
@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() } }) -
@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() } }) -
@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() } }) -
@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() } }) -
@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) } } } -
@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) } } } -
@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) } } } -
@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) } } } -
@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) } }) } -
@MOLSJEROEN HOW TO USE KTX repositories { google() } dependencies
{ implementation "androidx.core:core:1.2.0" } -
@MOLSJEROEN HOW TO USE KTX repositories { google() } dependencies
{ implementation "androidx.core:core:1.2.0" } -
@MOLSJEROEN HOW TO USE KTX repositories { google() } dependencies
{ implementation “androidx.core:core-ktx:1.2.0” } -
@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!!) }) -
@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) } -
@MOLSJEROEN ANIMATIONS val animator = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f) animator.doOnPause
{ doSomething(it) } animator.doOnResume { doSomething(it) } -
@MOLSJEROEN SYSTEM SERVICES // requires android M val layoutInflater =
context .getSystemService(LayoutInflater::class.java) -
@MOLSJEROEN SYSTEM SERVICES // requires android M val layoutInflater =
context .getSystemService(LayoutInflater::class.java) // any Android version context.getSystemService<LayoutInflater>() -
@MOLSJEROEN COLORS val red = Color.parseColor("#ff0000") val colorDrawable = ColorDrawable(red)
val red = "#ff0000".toColorInt() val colorDrawable = red.toDrawable() -
@MOLSJEROEN URI AND FILE val uri = “https://jeroenmols.com/blog”.toUri() val file
= uri.toFile() val fileUri = file.toUri() -
@MOLSJEROEN VIEW view.doOnLayout { } view.doOnNextLayout { } view.doOnPreDraw {
} view.doOnAttach { } view.doOnDetach { } view.marginStart view.marginEnd view.marginLeft view.marginRight view.marginTop view.marginBottom -
@MOLSJEROEN VIEW view.updateLayoutParams<ConstraintLayout.LayoutParams> { width = 200 // strong typed
access height = 200 } view.updatePadding(right = 10) // only assign one val bitmap = view.drawToBitmap() -
@MOLSJEROEN VIEW VISIBILITY if (view.visibility == View.VISIBLE) { // do
something } view.visibility = View.VISIBLE -
@MOLSJEROEN VIEW VISIBILITY if (view.visibility == View.VISIBLE) { // do
something } view.visibility = View.VISIBLE if (view.isVisible) { // do something } view.isVisible = false // View.GONE -
@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() }) -
@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() } -
@MOLSJEROEN VIEWMODELS - ACTIVITY // scoped to activity val viewmodel
= ViewModelProviders.of(this) .get(MyViewModel::class.java) -
@MOLSJEROEN VIEWMODELS - ACTIVITY // scoped to activity val viewmodel
= ViewModelProviders.of(this) .get(MyViewModel::class.java) // scoped to activity val viewModel by viewModels<MyViewModel>() -
@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) -
@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>() -
@MOLSJEROEN VIEWMODELS - FACTORY val viewModel = ViewModelProviders.of(this, VModelFactory()) .get(MyViewModel::class.java)
val viewModel by viewModels<MyViewModel>() { VModelFactory() } -
@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()) } -
@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 -
@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) -
@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) -
@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) -
@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") -
@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") -
@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) -
@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) -
@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() } -
@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() } -
@MOLSJEROEN IMAGE CREDITS Welcome image by Clement127 https://flic.kr/p/QdpUY8 Official Android
and Google logo's Font awesome https://fontawesome.com/