Using Kotlin/JS and Kotlin/Native on Android (Droidcon NYC 2019)

Using Kotlin/JS and Kotlin/Native on Android (Droidcon NYC 2019)

Kotlin's compiler backends, JVM, JS, and native, are each often associated with one platform: Android, the web, and iOS, respectively. But nothing about these three backends require that they be used on multiple platforms. Android is one of the few places where we have the potential to use all three!

This talk will explore using Kotlin/JS and Kotlin/Native in an Android app. First, we'll look at the motivations for why you might want to compile Kotlin to JS or native on Android. We'll cover the tools and libraries which will be necessary for integration into an app. Finally, we'll talk about the positive and negative architectural implications of using multiple Kotlin backends in your app.

Video: coming soon

E68309f117985270285ade8082f4877d?s=128

Jake Wharton

August 26, 2019
Tweet

Transcript

  1. Using Kotlin JS @JakeWharton and Kotlin Native on Android

  2. Kotlin Multiplatform

  3. Kotlin Multiplatform != Kotlin/JVM Kotlin/Native Kotlin/JS

  4. None
  5. None
  6. None
  7. None
  8. None
  9. None
  10. .apk

  11. .apk .dex

  12. .apk .dex

  13. .apk .js

  14. None
  15. None
  16. None
  17. None
  18. American Red Cross Payment to $redcross

  19. American Red Cross Payment to $redcross

  20. American Red Cross Payment to $redcross Donation

  21. American Red Cross Payment to $redcross Donation ~1 day

  22. American Red Cross Payment to $redcross Donation ~1 day 2

    - 3 days
  23. American Red Cross Payment to $redcross Donation ~1 day 2

    - 3 days 4 - 10 days
  24. None
  25. 1 - 10,000+ payments per user

  26. 1 - 10,000+ payments per user 20+ million users

  27. 1 - 10,000+ payments per user 20+ million users 10

    - 20 changes per year
  28. 1 - 10,000+ payments per user 20+ million users 10

    - 20 changes per year 1-5 KB per payment
  29. 1 - 10,000+ payments per user 20+ million users 10

    - 20 changes per year 1-5 KB per payment 20 - 100TB year
  30. 1 - 10,000+ payments per user 20+ million users 10

    - 20 changes per year 1-5 KB per payment 20 - 100TB year But no localization…
  31. "Payment to {cashtag}"

  32. "Payment to {cashtag}" "Donation to {cashtag}"

  33. "Payment to {cashtag}" "Donation to {cashtag}" No logic updates…

  34. "Payment to {cashtag}" "Donation to {cashtag}" No logic updates… Manual

    localization…
  35. None
  36. {"id":"Lgh13IUG7f6","amount": 137,"currency":"USD","sender": {"id":"2q3u9bUG8f6","username":"jake","display_name":"Jake Wharton","is_non_profit":false},"recipient": {"id":"h98FUH87gwe","username":"redcross","display_name":" American Red Cross","is_non_profit":true},"date":"2018-10-29T15:22:21"}

  37. {"id":"Lgh13IUG7f6","amount": 137,"currency":"USD","sender": {"id":"2q3u9bUG8f6","username":"jake","display_name":"Jake Wharton","is_non_profit":false},"recipient": {"id":"h98FUH87gwe","username":"redcross","display_name":" American Red Cross","is_non_profit":true},"date":"2018-10-29T15:22:21"} {"payment_line_1":"American Red

    Cross","payment_line_2":"Donation to $redcross","payment_line_3":"$1.37","payment_line_4":"Oct 29, 2018 at 3:22PM"}
  38. {"id":"Lgh13IUG7f6","amount": 137,"currency":"USD","sender": {"id":"2q3u9bUG8f6","username":"jake","display_name":"Jake Wharton","is_non_profit":false},"recipient": {"id":"h98FUH87gwe","username":"redcross","display_name":" American Red Cross","is_non_profit":true},"date":"2018-10-29T15:22:21"} {"payment_line_1":"American Red

    Cross","payment_line_2":"Donation to $redcross","payment_line_3":"$1.37","payment_line_4":"Oct 29, 2018 at 3:22PM"} if (payment.recipient.is_non_profit && payment.sender.is_florida_man) { return "Donation to %s" } return "Payment to %s
  39. {"id":"Lgh13IUG7f6","amount": 137,"currency":"USD","sender": {"id":"2q3u9bUG8f6","username":"jake","display_name":"Jake Wharton","is_non_profit":false},"recipient": {"id":"h98FUH87gwe","username":"redcross","display_name":" American Red Cross","is_non_profit":true},"date":"2018-10-29T15:22:21"} {"payment_line_1":"American Red

    Cross","payment_line_2":"Donation to $redcross","payment_line_3":"$1.37","payment_line_4":"Oct 29, 2018 at 3:22PM"} if (payment.recipient.is_non_pro && payment.sender.is_florida return "Donation to %s" } return "Payment to %s
  40. None
  41. None
  42. None
  43. None
  44. None
  45. None
  46. None
  47. &

  48. *.kt *.class

  49. *.kt *.class *.js *.so

  50. *.kt *.class *.js *.so

  51. *.kt *.class *.js *.so *.kt *.kt *.kt

  52. *.kt *.class *.js *.so *.kt *.kt *.kt

  53. *.kt *.class *.js *.so *.kt *.kt *.kt

  54. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) }
  55. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) } *.kt
  56. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) } interface List<out E> : Collection<E> { val size: Int fun contains(element: E): Boolean fun get(index: Int): E fun iterator(): Iterator<E> } *.kt
  57. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) } *.kt
  58. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) } Retrofit
  59. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) } Retrofit InMemoryUserService
  60. interface UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User

    @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) } Retrofit InMemoryUserService FS DB P2P RAM HTTP
  61. None
  62. &

  63. & *.kt

  64. & *.kt

  65. & *.kt

  66. & *.kt interface CoolStuff { fun doIt() }

  67. & *.kt interface CoolStuff { fun doIt() } class SomeCoolStuff

    { override fun doIt() { // ... } } Retrofit
  68. interface CoolStuff { suspend fun doIt() }

  69. interface CoolStuff { suspend fun doIt() }

  70. interface CoolStuff { suspend fun doIt() } .class

  71. interface CoolStuff { suspend fun doIt() } .class class SomeCoolStuff

    { override suspend fun doIt() { // ... } }
  72. interface CoolStuff { suspend fun doIt() } .class class SomeCoolStuff

    { override suspend fun doIt() { // ... } }
  73. interface CoolStuff { suspend fun doIt() } .class .class class

    SomeCoolStuff { override suspend fun doIt() { // ... } }
  74. interface CoolStuff { suspend fun doIt() } .class .class class

    SomeCoolStuff { override suspend fun doIt() { // ... } }
  75. interface CoolStuff { suspend fun doIt() } .class .js .class

    .so class SomeCoolStuff { override suspend fun doIt() { // ... } }
  76. interface CoolStuff { suspend fun doIt() } .class .js .class

    .so JS Bridge class SomeCoolStuff { override suspend fun doIt() { // ... } }
  77. interface CoolStuff { suspend fun doIt() } .class .js JNI

    Bridge .class .so JS Bridge class SomeCoolStuff { override suspend fun doIt() { // ... } }
  78. interface CoolStuff { suspend fun doIt() } class SomeCoolStuff {

    override suspend fun doIt() { // ... } } .class .js JNI Bridge .class .so JS Bridge
  79. interface CoolStuff { suspend fun doIt() } class SomeCoolStuff {

    override suspend fun doIt() { // ... } } .class .js JNI Bridge .class .so JS Bridge
  80. interface CoolStuff { suspend fun doIt() } class SomeCoolStuff {

    override suspend fun doIt() { // ... } } .class .js JNI Bridge .class .so JS Bridge
  81. interface CoolStuff { suspend fun doIt() } .class .js JNI

    Bridge .class .so JS Bridge class SomeCoolStuff { override suspend fun doIt() { // ... } }
  82. None
  83. None
  84. American Red Cross Donation to $redcross

  85. ├── payment-rendering/ │ ├── api/ │ │ ├── src/ │

    │ │ └── commonMain/ │ │ │ └── kotlin/ │ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ │ └── PaymentRenderer.kt │ │ └── build.gradle │ │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  86. ├── payment-rendering/ │ ├── api/ │ │ ├── src/ │

    │ │ └── commonMain/ │ │ │ └── kotlin/ │ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ │ └── PaymentRenderer.kt │ │ └── build.gradle │ │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  87. package com.example.dcnyc2019.payment.rendering interface PaymentRenderer { fun secondLine(tag: String, isNonProfit: Boolean):

    String }
  88. ├── payment-rendering/ │ ├── api/ │ │ ├── src/ │

    │ │ └── commonMain/ │ │ │ └── kotlin/ │ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ │ └── PaymentRenderer.kt │ │ └── build.gradle │ │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  89. ├── payment-rendering/ │ ├── api/ │ │ ├── src/ │

    │ │ └── commonMain/ │ │ │ └── kotlin/ │ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ │ └── PaymentRenderer.kt │ │ └── build.gradle │ │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  90. package com.example.dcnyc2019.payment.rendering class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag:

    String, isNonProfit: Boolean): String { } }
  91. package com.example.dcnyc2019.payment.rendering class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag:

    String, isNonProfit: Boolean): String { return buildString { } } }
  92. package com.example.dcnyc2019.payment.rendering class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag:

    String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") } } }
  93. package com.example.dcnyc2019.payment.rendering class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag:

    String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } }
  94. package com.example.dcnyc2019.payment.rendering class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag:

    String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } }
  95. package com.example.dcnyc2019.payment.rendering class PaymentRenderingImplTest { }

  96. package com.example.dcnyc2019.payment.rendering class PaymentRenderingImplTest { private val impl = PaymentRendererImpl()

    }
  97. package com.example.dcnyc2019.payment.rendering import kotlin.test.Test import kotlin.test.assertEquals class PaymentRenderingImplTest { private

    val impl = PaymentRendererImpl() @Test fun payment() { assertEquals("Payment to \$alec", impl.secondLine("alec", isNonProfit = false)) } }
  98. package com.example.dcnyc2019.payment.rendering import kotlin.test.Test import kotlin.test.assertEquals class PaymentRenderingImplTest { private

    val impl = PaymentRendererImpl() @Test fun payment() { assertEquals("Payment to \$alec", impl.secondLine("alec", isNonProfit = false)) } @Test fun donation() { assertEquals("Donation to \$redcross", impl.secondLine("redcross", isNonProfit = true)) } }
  99. package com.example.dcnyc2019.payment.rendering import kotlin.test.Test import kotlin.test.assertEquals class PaymentRenderingImplTest { private

    val impl = PaymentRendererImpl() @Test fun payment() { assertEquals("Payment to \$alec", impl.secondLine("alec", isNonProfit = false)) } @Test fun donation() { assertEquals("Donation to \$redcross", impl.secondLine("redcross", isNonProfit = true)) } }
  100. ├── payment-rendering/ │ ├── api/ │ │ ├── src/ │

    │ │ └── commonMain/ │ │ │ └── kotlin/ │ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ │ └── PaymentRenderer.kt │ │ └── build.gradle │ │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  101. ├── payment-rendering/ │ ├── api/ │ │ ├── src/ │

    │ │ └── commonMain/ │ │ │ └── kotlin/ │ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ │ └── PaymentRenderer.kt │ │ └── build.gradle │ │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  102. dependencies { implementation deps.kotlin.stdlib.jdk8 implementation deps.androidx.appcompat }

  103. dependencies { implementation deps.kotlin.stdlib.jdk8 implementation deps.androidx.appcompat implementation project(':payment-rendering:api') }

  104. dependencies { implementation deps.kotlin.stdlib.jdk8 implementation deps.androidx.appcompat implementation project(':payment-rendering:api') // Java

    bytecode impl embedded directly in dex like any other class. implementation project(':payment-rendering') }
  105. dependencies { implementation deps.kotlin.stdlib.jdk8 implementation deps.androidx.appcompat implementation project(':payment-rendering:api') // Java

    bytecode impl embedded directly in dex like any other class. implementation project(':payment-rendering') } // TODO embed JS... // TODO embed native...
  106. dependencies { implementation deps.kotlin.stdlib.jdk8 implementation deps.androidx.appcompat implementation project(':payment-rendering:api') // Java

    bytecode impl embedded directly in dex like any other class. implementation project(':payment-rendering') } // TODO embed JS... // TODO embed native...
  107. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) } }
  108. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) val binding = MainBinding.inflate(layoutInflater) setContentView(binding.root) } }
  109. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) val binding = MainBinding.inflate(layoutInflater) setContentView(binding.root) binding.run.setOnClickListener { } } }
  110. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) val binding = MainBinding.inflate(layoutInflater) setContentView(binding.root) binding.run.setOnClickListener { val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked -> TODO() binding.engineNative.isChecked -> TODO() else -> throw AssertionError() } } } }
  111. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) val binding = MainBinding.inflate(layoutInflater) setContentView(binding.root) binding.run.setOnClickListener { val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked -> TODO() binding.engineNative.isChecked -> TODO() else -> throw AssertionError() } binding.output.text = """ ${impl.secondLine("alec", false)} ${impl.secondLine("redcross", true)} """.trimIndent() } } }
  112. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) val binding = MainBinding.inflate(layoutInflater) setContentView(binding.root) binding.run.setOnClickListener { val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked -> TODO() binding.engineNative.isChecked -> TODO() else -> throw AssertionError() } binding.output.text = """ ${impl.secondLine("alec", false)} ${impl.secondLine("redcross", true)} """.trimIndent() } } }
  113. None
  114. None
  115. None
  116. None
  117. ((o) Duktape

  118. ((o) Duktape QuickJS

  119. ((o) Duktape QuickJS

  120. None
  121. interface Utf8 { String fromHex(String hex); } // Connect our

    interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting); // Note that Duktape.dec returns a Buffer, we must convert it to a String return value. duktape.evaluate("" + "var Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};");
  122. interface Utf8 { String fromHex(String hex); } duktape.evaluate("" + "var

    Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};"); // Connect our interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting);
  123. interface Utf8 { String fromHex(String hex); } duktape.evaluate("" + "var

    Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};"); // Connect our interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting);
  124. interface Utf8 { String fromHex(String hex); } duktape.evaluate("" + "var

    Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};"); // Connect our interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting);
  125. interface Utf8 { String fromHex(String hex); } duktape.evaluate("" + "var

    Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};"); // Connect our interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting);
  126. interface Utf8 { String fromHex(String hex); } duktape.evaluate("" + "var

    Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};"); // Connect our interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting);
  127. interface Utf8 { String fromHex(String hex); } duktape.evaluate("" + "var

    Utf8 = {\n" + " fromHex: function(v) { return String(Duktape.dec('hex', v)); }\n" + "};"); // Connect our interface to a JavaScript object called Utf8. Utf8 utf8 = duktape.get("Utf8", Utf8.class); // Call into the JavaScript object to decode a string. String greeting = utf8.fromHex("EC9588EB8595ED9598EC84B8EC9A9421"); Log.d("Greeting", greeting);
  128. ├── payment-rendering/ │ ├── api/ │ │ └── ... │

    │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  129. apply plugin: 'org.jetbrains.kotlin.multiplatform' kotlin { jvm() }

  130. apply plugin: 'org.jetbrains.kotlin.multiplatform' kotlin { jvm() js { nodejs() }

    }
  131. apply plugin: 'org.jetbrains.kotlin.multiplatform' kotlin { jvm() js { nodejs() }

    }
  132. ├── payment-rendering/ │ ├── api/ │ │ └── ... │

    │ │ ├── build/ │ │ └── libs/ │ │ ├── payment-rendering-js.jar │ │ └── payment-rendering-jvm.jar │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  133. (function (root, factory) { if (typeof define === 'function' &&

    define.amd) define(['exports', 'kotlin', 'DroidconNYC2019-api'], factory); else if (typeof exports === 'object') factory(module.exports, require('kotlin'), require('DroidconNYC2019-api')); else { if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'DroidconNYC2019-payment-rendering'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior t } if (typeof this['DroidconNYC2019-api'] === 'undefined') { throw new Error("Error loading module 'DroidconNYC2019-payment-rendering'. Its dependency 'DroidconNYC2019-api' was not found. Please, check whether 'DroidconNYC2 } root['DroidconNYC2019-payment-rendering'] = factory(typeof this['DroidconNYC2019-payment-rendering'] === 'undefined' ? {} : this['DroidconNYC2019-payment-rendering' } }(this, function (_, Kotlin, $module$DroidconNYC2019_api) { 'use strict'; var Kind_CLASS = Kotlin.Kind.CLASS; var PaymentRenderer = $module$DroidconNYC2019_api.com.example.dcnyc2019.payment.rendering.PaymentRenderer; var StringBuilder_init = Kotlin.kotlin.text.StringBuilder_init; function PaymentRendererImpl() { } PaymentRendererImpl.prototype.secondLine_ivxn3r$ = function (tag, isNonProfit) { var $receiver = StringBuilder_init(); $receiver.append_gw00v9$(isNonProfit ? 'Donation' : 'Payment'); $receiver.append_gw00v9$(' to $'); $receiver.append_gw00v9$(tag); return $receiver.toString(); }; PaymentRendererImpl.$metadata$ = { kind: Kind_CLASS, simpleName: 'PaymentRendererImpl', interfaces: [PaymentRenderer] }; var package$com = _.com || (_.com = {}); var package$example = package$com.example || (package$com.example = {}); var package$dcnyc2019 = package$example.dcnyc2019 || (package$example.dcnyc2019 = {}); var package$payment = package$dcnyc2019.payment || (package$dcnyc2019.payment = {}); var package$rendering = package$payment.rendering || (package$payment.rendering = {}); package$rendering.PaymentRendererImpl = PaymentRendererImpl; Kotlin.defineModule('DroidconNYC2019-payment-rendering', _); return _; }));
  134. var StringBuilder_init = Kotlin.kotlin.text.StringBuilder_init; function PaymentRendererImpl() { } PaymentRendererImpl.prototype.secondLine_ivxn3r$ =

    function (tag, isNonProfit) { var $receiver = StringBuilder_init(); $receiver.append_gw00v9$(isNonProfit ? 'Donation' : 'Payment'); $receiver.append_gw00v9$(' to $'); $receiver.append_gw00v9$(tag); return $receiver.toString(); }; PaymentRendererImpl.$metadata$ = { kind: Kind_CLASS, simpleName: 'PaymentRendererImpl', interfaces: [PaymentRenderer] }; var package$com = _.com || (_.com = {}); var package$example = package$com.example || (package$com.example = {}); var package$dcnyc2019 = package$example.dcnyc2019 || (package$example.dcnyc2019 = var package$payment = package$dcnyc2019.payment || (package$dcnyc2019.payment = {} var package$rendering = package$payment.rendering || (package$payment.rendering = package$rendering.PaymentRendererImpl = PaymentRendererImpl; Kotlin.defineModule('DroidconNYC2019-payment-rendering', _); return _; }));
  135. package com.example.dcnyc2019.payment.rendering interface PaymentRenderer { fun secondLine(tag: String, isNonProfit: Boolean):

    String }
  136. package com.example.dcnyc2019.payment.rendering import kotlin.js.JsName interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag:

    String, isNonProfit: Boolean): String }
  137. package com.example.dcnyc2019.payment.rendering import kotlin.js.JsName interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag:

    String, isNonProfit: Boolean): String }
  138. var StringBuilder_init = Kotlin.kotlin.text.StringBuilder_init; function PaymentRendererImpl() { } PaymentRendererImpl.prototype.secondLine_ivxn3r$ =

    function (tag, isNonProfit) { var $receiver = StringBuilder_init(); $receiver.append_gw00v9$(isNonProfit ? 'Donation' : 'Payment'); $receiver.append_gw00v9$(' to $'); $receiver.append_gw00v9$(tag); return $receiver.toString(); }; PaymentRendererImpl.$metadata$ = { kind: Kind_CLASS, simpleName: 'PaymentRendererImpl', interfaces: [PaymentRenderer] }; var package$com = _.com || (_.com = {}); var package$example = package$com.example || (package$com.example = {}); var package$dcnyc2019 = package$example.dcnyc2019 || (package$example.dcnyc2019 = var package$payment = package$dcnyc2019.payment || (package$dcnyc2019.payment = {} var package$rendering = package$payment.rendering || (package$payment.rendering = package$rendering.PaymentRendererImpl = PaymentRendererImpl; Kotlin.defineModule('DroidconNYC2019-payment-rendering', _); return _; }));
  139. var StringBuilder_init = Kotlin.kotlin.text.StringBuilder_init; function PaymentRendererImpl() { } PaymentRendererImpl.prototype.secondLine =

    function (tag, isNonProfit) { var $receiver = StringBuilder_init(); $receiver.append_gw00v9$(isNonProfit ? 'Donation' : 'Payment'); $receiver.append_gw00v9$(' to $'); $receiver.append_gw00v9$(tag); return $receiver.toString(); }; PaymentRendererImpl.$metadata$ = { kind: Kind_CLASS, simpleName: 'PaymentRendererImpl', interfaces: [PaymentRenderer] }; var package$com = _.com || (_.com = {}); var package$example = package$com.example || (package$com.example = {}); var package$dcnyc2019 = package$example.dcnyc2019 || (package$example.dcnyc2019 = var package$payment = package$dcnyc2019.payment || (package$dcnyc2019.payment = {} var package$rendering = package$payment.rendering || (package$payment.rendering = package$rendering.PaymentRendererImpl = PaymentRendererImpl; Kotlin.defineModule('DroidconNYC2019-payment-rendering', _); return _; }));
  140. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    TODO() binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  141. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() } TODO()
  142. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  143. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) duktape.evaluate(""" var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent()) } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  144. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) duktape.evaluate(""" var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent()) duktape.get("instance", PaymentRenderer::class.java) } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  145. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) duktape.evaluate(""" var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent()) duktape.get("instance", PaymentRenderer::class.java) } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  146. None
  147. None
  148. None
  149. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) duktape.evaluate(""" var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent()) duktape.get("instance", PaymentRenderer::class.java) } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  150. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) duktape.evaluate(""" var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent()) duktape.get("instance", PaymentRenderer::class.java) } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  151. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { hypotheticalKotlinJsLibrary.create<PaymentRenderer>() } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() } val duktape = Duktape.create() duktape.evaluate(assets.open("payment-rendering.js").use { it.readBytes().toString(UTF_8) }) duktape.evaluate(""" var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent()) duktape.get("instance", PaymentRenderer::class.java)
  152. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { hypotheticalKotlinJsLibrary.create<PaymentRenderer>() } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() }
  153. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

    { hypotheticalKotlinJsLibrary.create<PaymentRenderer>() } binding.engineNative.isChecked -> PaymentRendererJni() else -> throw AssertionError() } TODO()
  154. None
  155. None
  156. None
  157. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    } class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag: String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } } val jsBootstrap = """ var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent() class PaymentRendererJni : PaymentRenderer { // ... }
  158. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    } class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag: String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } } val jsBootstrap = """ var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent() class PaymentRendererJni : PaymentRenderer { // ... }
  159. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    } class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag: String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } } val jsBootstrap = """ var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent() class PaymentRendererJni : PaymentRenderer { // ... }
  160. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    } class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag: String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } } val jsBootstrap = """ var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent() class PaymentRendererJni : PaymentRenderer { // ... }
  161. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    } class PaymentRendererImpl : PaymentRenderer { override fun secondLine(tag: String, isNonProfit: Boolean): String { return buildString { append(if (isNonProfit) "Donation" else "Payment") append(" to $") append(tag) } } } val jsBootstrap = """ var instance = new this['DroidconNYC2019-payment-rendering']. com.example.dcnyc2019.payment.rendering.PaymentRendererImpl(); """.trimIndent() class PaymentRendererJni : PaymentRenderer { // ... }
  162. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    }
  163. interface PaymentRenderer { @JsName("secondLine") fun secondLine(tag: String, isNonProfit: Boolean): String

    }
  164. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering }

  165. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } JS/Native

    Bridge
  166. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } JS/Native

    Bridge JS/Native Implementation
  167. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } JS/Native

    Bridge JS/Native Implementation
  168. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } JS/Native

    Bridge JS/Native Implementation
  169. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } JS/Native

    Bridge JS/Native Implementation
  170. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } interface

    UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) }
  171. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } interface

    UserService { @GET("/user/{id}") suspend fun user(@Path("id") id: Long): User @POST("/user/{id}/image") suspend fun updateUserImage( @Path("id") id: Long, @Body image: RequestBody) }
  172. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

  173. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    JS/Native Bridge
  174. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    kotlinx.serialization JS/Native Bridge
  175. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    kotlinx.serialization JS/Native Bridge JS/Native Implementation
  176. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    kotlinx.serialization kotlinx.serialization JS/Native Bridge JS/Native Implementation
  177. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    kotlinx.serialization kotlinx.serialization JS/Native Bridge JS/Native Implementation
  178. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    kotlinx.serialization kotlinx.serialization kotlinx.serialization JS/Native Bridge JS/Native Implementation
  179. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    kotlinx.serialization kotlinx.serialization kotlinx.serialization JS/Native Bridge JS/Native Implementation
  180. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

    JS / native bridge JS / native implementation kotlinx.serialization JS/Native Bridge JS/Native Implementation
  181. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering }

  182. interface PaymentRenderer { @JsName("render") suspend fun render(payment: Payment): PaymentRendering }

  183. interface PaymentRenderer { @JsName("render") suspend fun render(payment: Payment): PaymentRendering }

  184. None
  185. None
  186. None
  187. None
  188. &

  189. & *.kt

  190. & *.kt interface CoolStuff { fun doIt() } class SomeCoolStuff

    { override fun doIt() { // ... } } JS bridge Native bridge
  191. Using Kotlin JS @JakeWharton and Kotlin Native on Android