Slide 1

Slide 1 text

Level up your extension game Aida Issayeva @aida_isay

Slide 2

Slide 2 text

Why we need it?

Slide 3

Slide 3 text

to make our lives easier

Slide 4

Slide 4 text

Example: styling a text

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

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 }

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

How to make extension?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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 )

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

How to use extension in java classes?

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

@InlineOnly When can you not use extensions in java?

Slide 21

Slide 21 text

Common practices

Slide 22

Slide 22 text

some sad…

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

some good…

Slide 34

Slide 34 text

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 }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Kotlin standard library is full examples of extension functions

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Thank you! Aida Issayeva @aida_isay