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

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

Jake Wharton

August 26, 2019
Tweet

More Decks by Jake Wharton

Other Decks in Programming

Transcript

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

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

    - 20 changes per year 1-5 KB per payment 20 - 100TB year
  3. 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…
  4. {"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
  5. {"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
  6. &

  7. 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) }
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. &

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

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

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

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

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

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

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

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

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

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

    Bridge .class .so JS Bridge class SomeCoolStuff { override suspend fun doIt() { // ... } }
  25. ├── 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
  26. ├── 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
  27. ├── 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
  28. ├── 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
  29. 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") } } }
  30. 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) } } }
  31. 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) } } }
  32. 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)) } }
  33. 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)) } }
  34. 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)) } }
  35. ├── 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
  36. ├── 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
  37. 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') }
  38. 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...
  39. 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...
  40. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

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

    super.onCreate(savedInstanceState) val binding = MainBinding.inflate(layoutInflater) setContentView(binding.root) binding.run.setOnClickListener { } } }
  42. 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() } } } }
  43. 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() } } }
  44. 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() } } }
  45. 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" + "};");
  46. 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);
  47. 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);
  48. 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);
  49. 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);
  50. 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);
  51. 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);
  52. ├── payment-rendering/ │ ├── api/ │ │ └── ... │

    │ │ ├── src/ │ │ └── commonMain/ │ │ └── kotlin/ │ │ └── com.example.dcnyc2019.payment.rendering/ │ │ └── PaymentRendererImpl.kt │ └── build.gradle │ ├── sample/ │ └── ... │ ├── build.gradle └── settings.gradle
  53. ├── 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
  54. (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 _; }));
  55. 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 _; }));
  56. 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 _; }));
  57. 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 _; }));
  58. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

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

    { val duktape = Duktape.create() } binding.engineNative.isChecked -> TODO() else -> throw AssertionError() } TODO()
  60. 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() }
  61. 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() }
  62. 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() }
  63. 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() }
  64. 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() }
  65. 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() }
  66. 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)
  67. val impl = when { binding.engineDex.isChecked -> PaymentRendererImpl() binding.engineJs.isChecked ->

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

    { hypotheticalKotlinJsLibrary.create<PaymentRenderer>() } binding.engineNative.isChecked -> PaymentRendererJni() else -> throw AssertionError() } TODO()
  69. 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 { // ... }
  70. 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 { // ... }
  71. 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 { // ... }
  72. 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 { // ... }
  73. 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 { // ... }
  74. 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) }
  75. 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) }
  76. interface PaymentRenderer { @JsName("render") fun render(payment: Payment): PaymentRendering } kotlinx.serialization

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

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

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

    kotlinx.serialization kotlinx.serialization kotlinx.serialization JS/Native Bridge JS/Native Implementation
  80. 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
  81. &

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

    { override fun doIt() { // ... } } JS bridge Native bridge