Slide 1

Slide 1 text

@MOLSJEROEN GETTING THE MOST OUT OF ANDROID KTX

Slide 2

Slide 2 text

@MOLSJEROEN

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

WHAT AND WHY

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

@MOLSJEROEN WHAT IS KTX

Slide 16

Slide 16 text

@MOLSJEROEN WHAT IS KTX

Slide 17

Slide 17 text

@MOLSJEROEN WHAT IS KTX

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

CRASH COURSE

Slide 22

Slide 22 text

@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!!) })

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

@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)

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

@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

Slide 49

Slide 49 text

WRITE YOUR OWN

Slide 50

Slide 50 text

@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)

Slide 51

Slide 51 text

@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)

Slide 52

Slide 52 text

@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)

Slide 53

Slide 53 text

@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")

Slide 54

Slide 54 text

@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")

Slide 55

Slide 55 text

@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)

Slide 56

Slide 56 text

@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)

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

DISCOVER MORE

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

WRAP UP

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

@MOLSJEROEN HTTPS://JEROENMOLS.COM/BLOG

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

MOLSJEROEN