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. 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 }
  2. 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.") } } } }
  3. data class DomesticAnimal( val id: String, val name: String, val

    farmLocation: String, val type: Type, val logo: String, val longDescription: String, val shortDescription: String )
  4. 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 )
  5. fun DomesticAnimal.toAnimal(): Animal { return Animal( id = id, name

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

    = DomesticAnimal( name = "Bactrian Camel", . . . farmLocation = "Aral Sea" ) val animal = domesticAnimal.toAnimal() }
  7. 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() ) }
  8. 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) }
  9. Modifying members of a class fun MainActivity.showWhiteLoadingIndicator() { mNumActiveLoadingIndicators++ runOnUiThread

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

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

    { setBackgroundColor( ContextCompat.getColor( context, colorResId ) ) }
  12. 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) }
  13. 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) }
  14. 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() } }
  15. Extend classes that being passed fun Activity.showDialog(message: String) { if

    (!isFinishing) { MaterialDialog.Builder(this) .content(message) .positiveText("Ok") .show() } }
  16. 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 }
  17. Missing iterators inline fun Menu.forEach(action: (item: MenuItem) -> Unit) {

    for (index in 0 until size()) { action(getItem(index)) } }
  18. 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) }