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

Kotlin Extensions and Beyond

Kotlin Extensions and Beyond

Ever since Google announced full Kotlin support for writing Android apps at I/O 2017, the language has taken the world by storm and many companies have started adopting it as their language of choice for Android development. One of the new exciting features introduced by Kotlin to Android are extension functions. In this talk, you will get an overview on how to create and use extension functions in your Kotlin code as well as how it interops with existing Java code. You will then be taken on a tour across common areas of Android where extension functions work wonders in making your code more concise and less prone to bugs. Transitioning from Java to Kotlin can be tricky so we will go through some of the best practices when doing the migration. By the end of the session, you will have a better understanding on what Kotlin and extension functions can do for you as well as some of the pitfalls that need to be kept in mind.

Pascal How

April 20, 2018
Tweet

More Decks by Pascal How

Other Decks in Technology

Transcript

  1. Kotlin Extensions Class Dog bark( ) wagTail( ) run( )

    Class Cat purr( ) scratch( ) jump( )
  2. Kotlin Extensions Class Dog bark( ) wagTail( ) run( )

    doTricks() follow(anotherDog) Class Cat purr( ) scratch( ) jump( ) break(preferablyGlass) tear(anyObject)
  3. Implementation And Usage class Dog(var name: String) { fun bark()

    { } fun wagTail() { } fun run() { } } // Elsewhere val ninja = Dog("Nin Ja") ninja.bark()
  4. Implementation And Usage class Dog(var name: String) { fun bark()

    { } fun wagTail() { } fun run() { } } // Elsewhere val ninja = Dog("Nin Ja") ninja.bark() val snoopy = Dog("Snoopy") ninja.follow(snoopy)
  5. Implementation And Usage // Usage val ninja = Dog("Nin Ja")

    ninja.bark() val snoopy = Dog("Snoopy") ninja.follow(snoopy) fun Dog.follow(dog: Dog) { Timber.d("${this.name} follows ${dog.name} to the park") } Receiver Class Receiver Object The Other Dog
  6. Implementation And Usage // Usage val ninja = Dog("Nin Ja")

    ninja.bark() val snoopy = Dog("Snoopy") ninja.follow(snoopy) D/MainActivity: Nin Ja barks D/MainActivity: Nin Ja follows Snoopy to the park
  7. Under The Hood // Kotlin code fun Dog.doTricks() { Timber.d("${this.name}

    does a trick!") } // Java Code public static final void doTricks(Dog dog) { Timber.d(dog.getName() + " does a trick!"); }
  8. Adding Static Functions class Dog(var name: String) { fun bark()

    { } fun wagTail() { } fun run() { } companion object }
  9. Adding Static Functions enum class Breed { PUG, DOBERMAN, POODLE,

    CORGI, ROTTWEILER, GERMAN_SHEPHERD } fun Dog.Companion.isGentle(breed: Breed): Boolean { return when (breed) { PUG, POODLE, CORGI, GERMAN_SHEPHERD -> true DOBERMAN, ROTTWEILER -> false } }
  10. Static Dispatching open class Dog(val name: String) { open fun

    run() { Timber.d("$name runs around the house") } } class Pug(name: String) : Dog(name) { override fun run() { Timber.d("Pugs prefer to walk instead") } }
  11. Static Dispatching fun Dog.doTricks() = Timber.d("${this.name} does a trick!") fun

    Pug.doTricks() = Timber.d("${this.name} fetches the ball")
  12. Static Dispatching val pug: Dog = Pug("Goofy") val otherPug =

    Pug("Dingo") pug.run() otherPug.run() pug.doTricks() otherPug.doTricks()
  13. Static Dispatching D/MainActivity: Pugs prefer to walk instead D/MainActivity: Pugs

    prefer to walk instead D/MainActivity: Goofy does a trick! D/MainActivity: Dingo fetches the ball
  14. Static Dispatching D/MainActivity: Pugs prefer to walk instead D/MainActivity: Pugs

    prefer to walk instead open class Dog(val name: String) { open fun run() { Timber.d("$name runs around the house") } } class Pug(name: String) : Dog(name) { override fun run() { Timber.d("Pugs prefer to walk instead") } }
  15. Static Dispatching D/MainActivity: Goofy does a trick! D/MainActivity: Dingo fetches

    the ball fun Dog.doTricks() = Timber.d("${this.name} does a trick!") fun Pug.doTricks() = Timber.d("${this.name} fetches the ball")
  16. Do You Test? fun String?.reverseOrderOfWords(): String { return this?.let {

    split(" ").reversed().joinToString(" ") } ?: "Cannot reverse this :(" }
  17. Do You Test? @Test fun `givenStringNotNull_whenReverseOrder_thenOutputWordsAreReversed`() { val message =

    "This is just a random string" val expectedString = "string random a just is This" val actualString = message.reverseOrderOfWords() assertEquals(actualString, expectedString) } @Test fun `givenNullString_whenReverseOrder_thenOutputErrorString`() { val message: String? = null val expectedString = "Cannot reverse this :(" val actualString = message.reverseOrderOfWords() assertEquals(actualString, expectedString) }
  18. Interoping With Java StringExt.kt fun String?.reverseOrderOfWords(): String { // Reverse

    order of words } Kotlin val message = "Kotlin extensions are cool!" val reversedMessage = message.reverseOrderOfWords()
  19. Interoping With Java StringExt.kt fun String?.reverseOrderOfWords(): String { // Reverse

    order of words } Java String message = "Kotlin extensions are cool!"; String reversedMessage = StringExtKt.reverseOrderOfWords(message);
  20. Interoping With Java StringExt.kt @file:JvmName("StringHelper") fun String?.reverseOrderOfWords(): String { //

    Reverse order of words } Java String message = "Kotlin extensions are cool!"; String reversedMessage = StringHelper.reverseOrderOfWords(message);
  21. Interoping With Java StringExt.kt @file:JvmName("StringHelper") fun String?.reverseOrderOfWords(): String { //

    Reverse order of words } Java String message = "Kotlin extensions are cool!"; String reversedMessage = StringHelper.reverseOrderOfWords(message);
  22. Interoping With Java Refactoring Existing Java Code // Java code

    somewhere somewhere String regex = "#{date}"; String title = "Travelling from London on #{date}"; String format = "dd MM YYYY" Long timestamp = 123456789L; String formattedTitle = StringHelper.replaceDate( "#{date}", // regex "Travelling from London on #{date}", // title "dd MM YYYY", // format 123456789L, // timestamp );
  23. Interoping With Java @file:JvmName("StringHelper") fun String.replaceDate(regex: String, format: String, timestamp:

    Long): String { return this.replace( regex, DateTimeFormat.forPattern(dateFormat) .print(DateTime(timestamp)) ) } org.junit.ComparisonFailure: Expected :Travelling from London on 02 01 1970 Actual :{#date}
  24. Interoping With Java String formattedTitle = StringHelper.replaceDate( regex, title, format,

    timestamp ); String formattedTitle = StringHelper.replaceDate( title, regex, format, timestamp );
  25. Higher Order Functions private fun performAtDogShow() { val trainer =

    Trainer() val dog = Dog(name) trainer.pats(dog) dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() trainer.treats(dog) }
  26. Higher Order Functions private fun performAtDogShow() { val trainer =

    Trainer() val dog = Dog(name) trainer.pats(dog) dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() trainer.treats(dog) }
  27. Higher Order Functions private fun unleash(name: String, doTricks: (Dog) ->

    Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) doTricks(dog) trainer.treats(dog) } private val doTricks: (Dog) -> Unit = { dog -> dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() }
  28. Higher Order Functions private fun unleash(name: String, doTricks: (Dog) ->

    Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) doTricks(dog) trainer.treats(dog) } private val doTricks: (Dog) -> Unit = { dog -> dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() }
  29. Higher Order Functions private val doTricks: (Dog) -> Unit =

    { dog -> dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() } private fun performAtDogShow() { unleash("Nin Ja", doTricks) }
  30. Higher Order Functions private val doTricks: (Dog) -> Unit =

    { dog -> dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() } private val doTricks: (Dog) -> Unit = { it.wagTail() it.fetchesBall() it.roll() it.doBackFlip() }
  31. Lambdas With Receivers private val doTricks: (Dog) -> Unit =

    { dog -> dog.wagTail() dog.fetchesBall() dog.roll() dog.doBackFlip() } private val doTricks: Dog.() -> Unit = { wagTail() fetchesBall() roll() doBackFlip() }
  32. Lambdas With Receivers private fun unleash(name: String, doTricks: (Dog) ->

    Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) doTricks(dog) trainer.treats(dog) } private fun unleash(name: String, doTricks: Dog.() -> Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) doTricks(dog) trainer.treats(dog) }
  33. Lambdas With Receivers private fun unleash(name: String, doTricks: (Dog) ->

    Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) doTricks(dog) trainer.treats(dog) } private fun unleash(name: String, doTricks: Dog.() -> Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) dog.doTricks() trainer.treats(dog) }
  34. Lambdas With Receivers private inline fun unleash(name: String, doTricks: Dog.()

    -> Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) dog.doTricks() trainer.treats(dog) }
  35. Lambdas With Receivers private final void unleash(String name, Function1 doTricks)

    { Trainer trainer = new Trainer(); Dog dog = new Dog(name); trainer.pats(dog); doTricks.invoke(dog); trainer.treats(dog); } private final void performAtDogShow() { String name$iv = "Ninja"; Trainer trainer$iv = new Trainer(); Dog dog$iv = new Dog(name$iv); trainer$iv.pats(dog$iv); dog$iv.wagTail(); dog$iv.fetchesBall(); dog$iv.roll(); dog$iv.doBackFlip(); trainer$iv.treats(dog$iv); } private final void unleash(String name, Function1 doTricks) { Trainer trainer = new Trainer(); Dog dog = new Dog(name); trainer.pats(dog); doTricks.invoke(dog); trainer.treats(dog); } private final void performAtDogShow() { this.unleash("Ninja", (Function1)null.INSTANCE); } Inline NO Inline
  36. Lambdas With Receivers private final void unleash(String name, Function1 doTricks)

    { Trainer trainer = new Trainer(); Dog dog = new Dog(name); trainer.pats(dog); doTricks.invoke(dog); trainer.treats(dog); } private val doTricks: Dog.() -> Unit = { wagTail() fetchesBall() roll() doBackFlip() }
  37. No Inline private final void performAtDogShow() { unleash("Ninja", new Function()

    { @Override public void invoke() { wagTail(); fetchesBall(); roll(); doBackFlip(); } }); }
  38. With Inline private final void unleash(String name, Function1 doTricks) {

    Trainer trainer = new Trainer(); Dog dog = new Dog(name); trainer.pats(dog); doTricks.invoke(dog); trainer.treats(dog); } private val doTricks: Dog.() -> Unit = { wagTail() fetchesBall() roll() doBackFlip() }
  39. With Inline private final void unleash(String name, Function1 doTricks) {

    Trainer trainer = new Trainer(); Dog dog = new Dog(name); trainer.pats(dog); doTricks.invoke(dog); trainer.treats(dog); } private val doTricks: Dog.() -> Unit = { wagTail() fetchesBall() roll() doBackFlip() }
  40. With Inline private final void performAtDogShow() { String name$iv =

    "Ninja"; Trainer trainer$iv = new Trainer(); Dog dog$iv = new Dog(name$iv); trainer$iv.pats(dog$iv); dog$iv.wagTail(); dog$iv.fetchesBall(); dog$iv.roll(); dog$iv.doBackFlip(); trainer$iv.treats(dog$iv); }
  41. Lambdas With Receivers private final void performAtDogShow() { String name$iv

    = "Ninja"; Trainer trainer$iv = new Trainer(); Dog dog$iv = new Dog(name$iv); trainer$iv.pats(dog$iv); dog$iv.wagTail(); dog$iv.fetchesBall(); dog$iv.roll(); dog$iv.doBackFlip(); trainer$iv.treats(dog$iv); } private final void performAtDogShow() { this.unleash( "Ninja", (Function1) null.INSTANCE ); } Inline NO Inline
  42. Lambdas With Receivers private fun performAtDogShow() { unleash("Nin Ja", doTricks)

    } private val doTricks: Dog.() -> Unit = { wagTail() fetchesBall() roll() doBackFlip() } private inline fun unleash(name: String, doTricks: Dog.() -> Unit) { val trainer = Trainer() val dog = Dog(name) trainer.pats(dog) dog.doTricks() trainer.treats(dog) }
  43. Lambdas With Receivers private fun performAtDogShow() { unleash("Nin Ja", doTricks)

    } D/Dog: Nin Ja wags tail D/Dog: Nin Ja fetches ball D/Dog: Nin Ja rolls D/Dog: Nin Ja does a backflip
  44. Extend All The Things Android Views fun View.setVisible(visible: Boolean) {

    this.visibility = if (visible) View.VISIBLE else View.GONE } // Elsewhere myButtonView.setVisible(true) myImageView.setVisible(false)
  45. Extend All The Things SharedPreferences // Classic usage of SharedPreferences

    fun saveDog() { prefs.edit { putString("name", "Nin Ja") putInt("age", 7) } } fun saveDog() { val editor = prefs.edit() editor.putString("name", "Nin Ja") editor.putInt("age", 7) editor.apply() }
  46. Extend All The Things SharedPreferences fun SharedPreferences.edit(editor: SharedPreferences.Editor, action: ()

    -> Unit) { action() } fun saveDog() { val editor = prefs.edit() editor.putString("name", "Nin Ja") editor.putInt("age", 7) editor.apply() }
  47. Extend All The Things SharedPreferences fun saveDog() { val editor

    = prefs.edit() prefs.edit(editor) { editor.putString("name", "Nin Ja") editor.putInt("age", 7) } editor.apply() } fun SharedPreferences.edit(editor: SharedPreferences.Editor, action: () -> Unit) { action() }
  48. Extend All The Things SharedPreferences fun saveDog() { val editor

    = prefs.edit() prefs.edit(editor) { editor.putString("name", "Nin Ja") editor.putInt("age", 7) } editor.apply() } fun SharedPreferences.edit(editor: SharedPreferences.Editor, action: () -> Unit) { action() }
  49. Extend All The Things SharedPreferences fun saveDog() { prefs.edit(editor) {

    editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(editor: SharedPreferences.Editor, action: () -> Unit) { val editor = edit() action() editor.apply() }
  50. Extend All The Things SharedPreferences fun saveDog() { prefs.edit(editor) {

    editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(editor: SharedPreferences.Editor, action: () -> Unit) { val editor = edit() action() editor.apply() }
  51. Extend All The Things SharedPreferences fun saveDog() { prefs.edit() {

    editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: () -> Unit) { val editor = edit() action() editor.apply() }
  52. Extend All The Things SharedPreferences fun saveDog() { prefs.edit() {

    editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: () -> Unit) { val editor = edit() action() editor.apply() }
  53. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    putString("name", "Nin Ja") putInt("age", 7) } } fun saveDog() { prefs.edit { editor -> editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: (SharedPreferences.Editor) -> Unit) { val editor = edit() action(editor) editor.apply() }
  54. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    putString("name", "Nin Ja") putInt("age", 7) } } fun saveDog() { prefs.edit { editor -> editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: (SharedPreferences.Editor) -> Unit) { val editor = edit() action(editor) editor.apply() }
  55. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    putString("name", "Nin Ja") putInt("age", 7) } } fun saveDog() { prefs.edit { editor -> editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: (SharedPreferences.Editor) -> Unit) { val editor = edit() action(editor) editor.apply() }
  56. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    editor -> editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: (SharedPreferences.Editor) -> Unit) { val editor = edit() action(editor) editor.apply() }
  57. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    editor -> editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) { val editor = edit() action(editor) editor.apply() }
  58. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    editor -> editor.putString("name", "Nin Ja") editor.putInt("age", 7) } } fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) { val editor = edit() action(editor) editor.apply() }
  59. Extend All The Things SharedPreferences fun saveDog() { prefs.edit {

    putString("name", "Nin Ja") putInt("age", 7) } } fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) { val editor = edit() editor.action() editor.apply() }
  60. Extend All The Things SharedPreferences // More Kotlinesque code fun

    saveDog() { prefs.edit { putString("name", "Nin Ja") putInt("age", 7) } } fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) { val editor = edit() editor.action() editor.apply() }
  61. Extend All The Things SharedPreferences // More Kotlinesque code fun

    saveDog() { prefs.edit { putString("name", "Nin Ja") putInt("age", 7) } } inline fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) { val editor = edit() editor.action() editor.apply() }
  62. Extend All The Things fun Context.loadImageCenterCrop(url: String, imageView: ImageView) {

    Picasso.withContext(this) .load(url) .centerCrop() .fit() .into(imageView) } // Elsewhere context.loadImageCenterCrop("http://example.com/image.jpg", myImageView)
  63. Extend All The Things fun Context.toast(message: CharSequence, duration: Int =

    Toast.LENGTH_SHORT): Toast { return Toast.makeText(this, message, duration).apply { show() } } // Elsewhere toast("Message sent!")
  64. Extend All The Things fun View.showDialog(activity: Activity) { val builder

    = AlertDialog.Builder(activity) builder.setView(this) builder.setIcon(R.drawable.ic_launcher_background) builder.setTitle("Alert Dialog") builder.setMessage("This is bad use of Kotlin extensions") builder.setPositiveButton("OK", null) builder.setNegativeButton("Cancel", null) builder.create() builder.show() } // Elsewhere myCustomView.showDialog(this)
  65. Abusing Extension Functions fun Context.loadImageCenterCrop(url: String, imageView: ImageView) { Picasso.withContext(this)

    .load(url) .centerCrop() .fit() .into(imageView) } fun ImageView.loadImageCenterCrop(url: String) { Picasso.withContext(this.getContext()) .load(url) .centerCrop() .fit() .into(this) }
  66. Abusing Extension Functions fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT):

    Toast { return Toast.makeText(this, message, duration).apply { show() } } // Elsewhere toast("Message sent!")
  67. Abusing Extension Functions fun Any.showToast(context: Context, duration: Int = Toast.LENGTH_SHORT):

    Toast { return Toast.makeText(context, this.toString(), duration).apply { show() } } // Elsewhere "Good morning Pisa!".showToast(context) "Good morning ${conference.name}!".showToast(context)
  68. Abusing Extension Functions fun View.showDialog(activity: Activity) { val builder =

    AlertDialog.Builder(activity) builder.setView(this) builder.setIcon(R.drawable.ic_launcher_background) builder.setMessage("Abusing Kotlin extensions") builder.setPositiveButton("OK", null) builder.create() builder.show() } fun showDialog(activity: Activity, customView: View) { // Customise dialog here }
  69. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX
  70. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX
  71. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX
  72. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX
  73. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX
  74. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX
  75. Tips Naming Conventions For Extension Files Decide On Extension Files

    Locations Avoid Repetition Don’t Get Carried Away Consider Creating A Centralised Resource Don’t Use Same Signature As Member Functions Android KTX