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

Level up your extension game

Level up your extension game

November 2019
Droidcon San Francisco: https://www.sf.droidcon.com/speaker/Aida-Issayeva

In this talk I'll go over some good and bad practices about using extensions in Kotlin, as well as use cases for the Kotlin's standard and Android KTX extension functions.
Link to sample code: https://github.com/AidaIssayeva/Level-up-your-extension-game

9efd6e1957baf11d3740d07d06ee5d76?s=128

Aida Issayeva

November 26, 2019
Tweet

More Decks by Aida Issayeva

Other Decks in Programming

Transcript

  1. Level up your extension game Aida Issayeva @aida_isay

  2. Why we need it?

  3. to make our lives easier

  4. Example: styling a text

  5. None
  6. Without extensions: private fun spansWithoutExtensions(): SpannableStringBuilder { val firstPart =

    getString(R.string.what_is_android) val spannableStringBuilder = SpannableStringBuilder() spannableStringBuilder.append(firstPart) spannableStringBuilder.append("\n") spannableStringBuilder.setSpan( StyleSpan(Typeface.BOLD), 0, firstPart.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE ) spannableStringBuilder.setSpan( RelativeSizeSpan(2f), 0, firstPart.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE ) spannableStringBuilder.setSpan( ForegroundColorSpan(Color.WHITE), 0, firstPart.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE ) val secondPart = getString(R.string.what_is_android_answer) spannableStringBuilder.append(secondPart) spannableStringBuilder.setSpan( ForegroundColorSpan( ContextCompat.getColor( this@MainActivity, R.color.colorAccent ) ), firstPart.length, firstPart.length + secondPart.length, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannableStringBuilder.append("\n") val thirdPart = getString(R.string.faq) } spannableStringBuilder.append(thirdPart) spannableStringBuilder.setSpan( ForegroundColorSpan( ContextCompat.getColor( this@MainActivity, R.color.dirty_white ) ), beginning, ending, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannableStringBuilder.append(" ") val clickableText = "Android website." spannableStringBuilder.append(clickableText) spannableStringBuilder.setSpan( object : ClickableSpan() { override fun onClick(widget: View) { //open webview val url = getString(R.string.android_website) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } override fun updateDrawState(ds: TextPaint) { super.updateDrawState(ds) ds.isUnderlineText = true } }, spannableStringBuilder.length - clickableText.length, spannableStringBuilder.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE ) spannableStringBuilder.setSpan( ForegroundColorSpan(Color.GREEN), spannableStringBuilder.length - clickableText.length, spannableStringBuilder.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE ) return spannableStringBuilder }
  7. With extensions: private fun spansWithExtensions(): SpannedString { return buildSpannedString {

    bold { color(Color.WHITE) { scale(2f) { append(getString(R.string.what_is_android)) } } } lineBreak() color(ContextCompat.getColor(this@MainActivity, R.color.colorAccent)) { append( getString(R.string.what_is_android_answer) ) } lineBreak() color( ContextCompat.getColor( this@MainActivity, R.color.dirty_white ) ) { append(getString(R.string.faq)) } space() underline { inSpans( object : ClickableSpan() { override fun onClick(widget: View) { //open webview val url = getString(R.string.android_website) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } }, ForegroundColorSpan(Color.GREEN) ) { append("Android website.") } } } }
  8. How to make extension?

  9. data class DomesticAnimal( val id: String, val name: String, val

    farmLocation: String, val type: Type, val logo: String, val longDescription: String, val shortDescription: String )
  10. data class DomesticAnimal( val id: String, val name: String, val

    farmLocation: String, val type: Type, val logo: String, val longDescription: String, val shortDescription: String ) data class Animal( val id: String, val name: String, val isDomestic: Boolean, val inRedList: Boolean, val description: String, val logo: String )
  11. fun DomesticAnimal.toAnimal(): Animal { return Animal( id = id, name

    = name, logo = logo, isDomestic = true, description = shortDescription, inRedList = false ) }
  12. override fun onCreate(savedInstanceState: Bundle?) { . . . val domesticAnimal

    = DomesticAnimal( name = "Bactrian Camel", . . . farmLocation = "Aral Sea" ) val animal = domesticAnimal.toAnimal() }
  13. How to use extension in java classes?

  14. None
  15. None
  16. None
  17. None
  18. None
  19. None
  20. @InlineOnly When can you not use extensions in java?

  21. Common practices

  22. some sad…

  23. Business logic on android and java common types fun CharSequence.toAnimal():

    Animal { return Animal( id = this.toString(), name = this.toString(), logo = "", isDomestic = false, inRedList = false, description = this.toString() ) }
  24. Do business to business or common to common instead fun

    WildAnimal.toAnimal(): Animal { return Animal( id = id, name = name, logo = logo, isDomestic = false, description = shortDescription, inRedList = inRedList ) } @kotlin.internal.InlineOnly public inline fun String.toPattern(flags: Int = 0): java.util.regex.Pattern { return java.util.regex.Pattern.compile(this, flags) }
  25. Modifying members of a class fun MainActivity.showWhiteLoadingIndicator() { mNumActiveLoadingIndicators++ runOnUiThread

    { . . . } } class MainActivity : AppCompatActivity() { var mNumActiveLoadingIndicators: Int = 0 }
  26. Make a member function instead class MainActivity : AppCompatActivity() {

    var mNumActiveLoadingIndicators: Int = 0 fun showWhiteLoadingIndicator() { mNumActiveLoadingIndicators++ runOnUiThread { . . . } }
  27. Using the same name as member function fun View.setBackgroundColor(colorResId: Int)

    { setBackgroundColor( ContextCompat.getColor( context, colorResId ) ) }
  28. Use different name instead fun View.setBackgroundColorResId(colorId: Int) { setBackgroundColor( ContextCompat.getColor(

    context, colorId ) ) }
  29. Extending Context on every occasion fun Context.showGif(imageView: ImageView, url: String)

    { Glide.with(this) .asGif() .listener(object : RequestListener<GifDrawable> { override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<GifDrawable>?, isFirstResource: Boolean ) = false override fun onResourceReady( resource: GifDrawable?, model: Any?, target: Target<GifDrawable>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { resource?.setLoopCount(1) return false } }) .load(url) .into(imageView) }
  30. Extend views that being passed fun ImageView.asGif(url: String) { Glide.with(this.context)

    .asGif() .listener(object : RequestListener<GifDrawable> { override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<GifDrawable>?, isFirstResource: Boolean ) = false override fun onResourceReady( resource: GifDrawable?, model: Any?, target: Target<GifDrawable>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { resource?.setLoopCount(1) return false } }) .load(url) .into(this) }
  31. Extending Context on every occasion fun Context.showDialog(message: String, activity: Activity?)

    { if (activity != null && !activity.isFinishing) { MaterialDialog.Builder(this) .content(message) .positiveText("Ok") .show() } }
  32. Extend classes that being passed fun Activity.showDialog(message: String) { if

    (!isFinishing) { MaterialDialog.Builder(this) .content(message) .positiveText("Ok") .show() } }
  33. some good…

  34. Type conversion public fun <K, V> Map<out K, V>.toList(): List<Pair<K,

    V>> { if (size == 0) return emptyList() val iterator = entries.iterator() if (!iterator.hasNext()) return emptyList() val first = iterator.next() if (!iterator.hasNext()) return listOf(first.toPair()) val result = ArrayList<Pair<K, V>>(size) result.add(first.toPair()) do { result.add(iterator.next().toPair()) } while (iterator.hasNext()) return result }
  35. Interface conversion public fun <K, V> Map<out K, V>.asSequence(): Sequence<Map.Entry<K,

    V>> { return entries.asSequence() }
  36. Missing iterators inline fun Menu.forEach(action: (item: MenuItem) -> Unit) {

    for (index in 0 until size()) { action(getItem(index)) } }
  37. Nullable receiver public actual fun String?.equals(other: String?, ignoreCase: Boolean =

    false): Boolean { if (this === null) return other === null return if (!ignoreCase) (this as java.lang.String).equals(other) else (this as java.lang.String).equalsIgnoreCase(other) }
  38. Kotlin standard library is full examples of extension functions

  39. core-ktx comes out of box in recent versions of Android

    Studio
  40. References: https://kotlinlang.org/api/latest/jvm/stdlib https://developer.android.com/kotlin/ktx https://jakewharton.com/android-ktx

  41. Thank you! Aida Issayeva @aida_isay