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

Droidcon SF: Level up your extension game

Droidcon SF: Level up your extension game

PRESENTED AT:
Droidcon San Francisco
https://www.sf.droidcon.com/speaker/Aida-Issayeva

DATE:
November 26, 2019

DESCRIPTION:
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

MORE TALKS & ARTICLES FROM ME: https://cupsofcode.com/talks/

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

    View Slide

  2. Why we need it?

    View Slide

  3. to make our lives
    easier

    View Slide

  4. Example:

    styling a text

    View Slide

  5. View Slide

  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(
    [email protected],
    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(
    [email protected],
    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
    }

    View Slide

  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([email protected], R.color.colorAccent)) {
    append(
    getString(R.string.what_is_android_answer)
    )
    }
    lineBreak()
    color(
    ContextCompat.getColor(
    [email protected],
    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.")
    }
    }
    }
    }

    View Slide

  8. How to make extension?

    View Slide

  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
    )

    View Slide

  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
    )

    View Slide

  11. fun DomesticAnimal.toAnimal(): Animal {
    return Animal(
    id = id,
    name = name,
    logo = logo,
    isDomestic = true,
    description = shortDescription,
    inRedList = false
    )
    }

    View Slide

  12. override fun onCreate(savedInstanceState: Bundle?) {
    . . .
    val domesticAnimal = DomesticAnimal(
    name = "Bactrian Camel",
    . . .
    farmLocation = "Aral Sea"
    )
    val animal = domesticAnimal.toAnimal()
    }

    View Slide

  13. How to use extension in java classes?

    View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. @InlineOnly
    When can you not use extensions in java?

    View Slide

  21. Common practices

    View Slide

  22. some sad…

    View Slide

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

    View Slide

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

    View Slide

  25. Modifying members of a class
    fun MainActivity.showWhiteLoadingIndicator() {
    mNumActiveLoadingIndicators++
    runOnUiThread {
    . . .
    }
    }
    class MainActivity : AppCompatActivity() {
    var mNumActiveLoadingIndicators: Int = 0
    }

    View Slide

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

    View Slide

  27. Using the same name as member function
    fun View.setBackgroundColor(colorResId: Int) {
    setBackgroundColor(
    ContextCompat.getColor(
    context, colorResId
    )
    )
    }

    View Slide

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

    View Slide

  29. Extending Context on every occasion
    fun Context.showGif(imageView: ImageView, url: String) {
    Glide.with(this)
    .asGif()
    .listener(object : RequestListener {
    override fun onLoadFailed(
    e: GlideException?,
    model: Any?,
    target: Target?,
    isFirstResource: Boolean
    ) = false
    override fun onResourceReady(
    resource: GifDrawable?,
    model: Any?,
    target: Target?,
    dataSource: DataSource?,
    isFirstResource: Boolean
    ): Boolean {
    resource?.setLoopCount(1)
    return false
    }
    })
    .load(url)
    .into(imageView)
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. some good…

    View Slide

  34. Type conversion
    public fun Map.toList(): List> {
    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>(size)
    result.add(first.toPair())
    do {
    result.add(iterator.next().toPair())
    } while (iterator.hasNext())
    return result
    }

    View Slide

  35. Interface conversion
    public fun Map.asSequence():
    Sequence> {
    return entries.asSequence()
    }

    View Slide

  36. Missing iterators
    inline fun Menu.forEach(action: (item: MenuItem) -> Unit) {
    for (index in 0 until size()) {
    action(getItem(index))
    }
    }

    View Slide

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

    View Slide

  38. Kotlin standard library is full examples of extension functions

    View Slide

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

    View Slide

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

    View Slide

  41. Thank you!
    Aida Issayeva
    @aida_isay

    View Slide