A Journey in Shared Code with Kotlin Multiplatform [droidcon Berlin 2019]

A Journey in Shared Code with Kotlin Multiplatform [droidcon Berlin 2019]

Hear the story of how we shipped Kotlin Multiplatform across Android, iOS, and the Web to power Quizlet’s learning experience for over 50 million users per month. By pinpointing the best areas of our already mature codebase to share (and intentionally skipping over many others), Quizlet was able to use Kotlin Multiplatform to share the “secret sauce” of our product without shackling our Android, iOS, and Web clients to a predetermined architecture.

We drastically reduced the size of our Android app, achieved performance and stability wins across both iOS and Android, simplified our Web build process, and unlocked the ability for mobile and backend engineers to work confidently on this shared code -- but we also faced several speed bumps along the way. Learn how we overcame challenges such as platform-specific quirks of the Kotlin standard library, constantly evolving tooling, and many more to make the magic happen!

89f7862e003b32b4fad41f36e9d3843c?s=128

Ankush Gupta

July 03, 2019
Tweet

Transcript

  1. A Journey in Shared Code with Kotlin Multiplatform @ankushg @quizlet

  2. A Journey in Shared Code with Kotlin Multiplatform* @ankushg @quizlet

  3. A Brief History of Quizlet @ankushg @quizlet

  4. A Brief History of Quizlet • 2005: quizlet.com @ankushg @quizlet

  5. A Brief History of Quizlet • 2005: quizlet.com • 2012:

    iOS @ankushg @quizlet
  6. A Brief History of Quizlet • 2005: quizlet.com • 2012:

    iOS • 2013: Android @ankushg @quizlet
  7. Time Doesn't Just Stop While we were busy writing the

    mobile apps... @ankushg @quizlet
  8. Time Doesn't Just Stop While we were busy writing the

    mobile apps... • tens of millions of users joined the website @ankushg @quizlet
  9. Time Doesn't Just Stop While we were busy writing the

    mobile apps... • tens of millions of users joined the website • creating millions of study sets @ankushg @quizlet
  10. Time Doesn't Just Stop While we were busy writing the

    mobile apps... • tens of millions of users joined the website • creating millions of study sets • spanning thousands of different subjects @ankushg @quizlet
  11. Time Doesn't Just Stop While we were busy writing the

    mobile apps... • tens of millions of users joined the site • creating millions of study sets • spanning thousands of different subjects and the work on our "secret sauce" had taken off. @ankushg @quizlet
  12. The Secret Sauce makes us more than just a CRUDdy

    app 1 Shane Mooney - Spaced Repetition for All: Cognitive Science Meets Big Data in a Procrastinating World
  13. The Secret Sauce makes us more than just a CRUDdy

    app • codified enums and logging events 1 Shane Mooney - Spaced Repetition for All: Cognitive Science Meets Big Data in a Procrastinating World
  14. The Secret Sauce makes us more than just a CRUDdy

    app • codified enums and logging events • context-dependent grading rule system 1 Shane Mooney - Spaced Repetition for All: Cognitive Science Meets Big Data in a Procrastinating World
  15. The Secret Sauce makes us more than just a CRUDdy

    app • codified enums and logging events • context-dependent grading rule system • modeling the user's brain to help them learn better1 1 Shane Mooney - Spaced Repetition for All: Cognitive Science Meets Big Data in a Procrastinating World
  16. Actual Answer Submission Language Correctness "ham + cheese" "ham and

    cheese" English ✅ "x + y" "x and y" Math ❌ "nǐhǎo" "ni3ha3o" Chinese ✅ "gin/tonic" "tonic/gin" English ✅
  17. Actual Answer Submission Correctness Suggestion "apple, banana" "apple" ❌ Enable

    ACCEPT PARTIAL ANSWERS to mark as correct "to receive" "to recieve" ❌ Enable ACCEPT ANSWERS WITH TYPOS to mark as correct
  18. Android was stuck playing catch-up

  19. Slow down or double-down?

  20. The Secret Sauce makes us more than just a CRUDdy

    app • codified enums and logging events • context-dependent grading rule system • modeling the user's brain to help them learn better1 1 Shane Mooney - Spaced Repetition for All: Cognitive Science Meets Big Data in a Procrastinating World
  21. The Secret Sauce makes us more than just a CRUDdy

    app • codified enums and logging events • context-dependent grading rule system • modeling the user's brain to help them learn better1 ‐ this is what should be shared. 1 Shane Mooney - Spaced Repetition for All: Cognitive Science Meets Big Data in a Procrastinating World
  22. How We Did It

  23. Enter Shared Javascript

  24. Enter Shared Javascript • In-browser on Web

  25. Enter Shared Javascript • In-browser on Web • JavascriptCore on

    iOS
  26. Enter Shared Javascript • In-browser on Web • JavascriptCore on

    iOS • ??? on Android
  27. Enter Shared Javascript • In-browser on Web • JavascriptCore on

    iOS • J2V8 on Android2 2 Damien Diehl - Comparison Shopping: Searching for JavaScript Engines for Android
  28. Shared Code Pinyin-Converter Grader Test-Generator Learn UTM-Params-Helper @ankushg @quizlet

  29. Not without its problems @ankushg @quizlet

  30. Not without its problems • performance @ankushg @quizlet

  31. Not without its problems • performance • APK size @ankushg

    @quizlet
  32. Not without its problems • performance • APK size •

    stability @ankushg @quizlet
  33. Not without its problems • performance • APK size •

    stability • debuggability @ankushg @quizlet
  34. Not without its problems • performance • APK size •

    stability • debuggability • developer confidence @ankushg @quizlet
  35. Not without its problems • performance • APK size •

    stability • debuggability • developer confidence But it got the job done. @ankushg @quizlet
  36. Not without its problems • performance • APK size •

    stability • debuggability • developer confidence But it got the job done... for years. @ankushg @quizlet
  37. A new challenger approaches...

  38. A new challenger approaches... Kotlin Multiplatform

  39. None
  40. Rethinking Shared Code

  41. Shared Code Pinyin-Converter Grader Test-Generator Learn UTM-Params-Helper @ankushg @quizlet

  42. Web Code Pinyin-Converter Grader Test-Generator Learn UTM-Params-Helper @ankushg @quizlet

  43. Android Web iOS Pinyin-Converter Grader Test-Generator Learn UTM-Params-Helper @ankushg @quizlet

  44. Shared Android Web iOS Pinyin-Converter Grader Test-Generator Learn UTM-Params- Helper

    @ankushg @quizlet
  45. Is It Actually Viable? @ankushg @quizlet

  46. Is It Actually Viable? • performance @ankushg @quizlet

  47. Is It Actually Viable? • performance • bundle size @ankushg

    @quizlet
  48. Is It Actually Viable? • performance • bundle size •

    stability @ankushg @quizlet
  49. Is It Actually Viable? • performance • bundle size •

    stability • debuggability @ankushg @quizlet
  50. Is It Actually Viable? • performance • bundle size •

    stability • debuggability • developer confidence @ankushg @quizlet
  51. Shared Android Web iOS Grader Everything Else @ankushg @quizlet

  52. Shared Android Web iOS Grader ? Everything Else @ankushg @quizlet

  53. Compilation ≠ Correctness @ankushg @quizlet

  54. Compilation ≠ Correctness • kotlin.test to port existing test suite

    @ankushg @quizlet
  55. Compilation ≠ Correctness • kotlin.test to port existing test suite

    • BigQuery UDFs to validate with real-world data @ankushg @quizlet
  56. Shared Android Web iOS Grader Everything Else @ankushg @quizlet

  57. Android Benchmarks Benchmark JS + J2V8 (ms) Kotlin/JVM (ms) (ms)

    Load Time 1654 167 -1487 Marshalling 690 10 -680 Grading 838 238 -600 @ankushg @quizlet
  58. Shared Android Web iOS Grader ? Everything Else @ankushg @quizlet

  59. Kotlin/JS Unknowns • compiling, testing, deploying, debugging @ankushg @quizlet

  60. Kotlin/JS Unknowns • compiling, testing, deploying, debugging • JS interop

    @ankushg @quizlet
  61. Kotlin/JS Unknowns • compiling, testing, deploying, debugging • JS interop

    • bundle size • no ES2015 modules = no webpack tree-shaking • Kotlin/JS Dead Code Elimination (DCE)3 ➡ 30kb 3 https:/ /kotlinlang.org/docs/reference/javascript-dce.html
  62. Kotlin/JS Unknowns • compiling, testing, deploying, debugging • JS interop

    • bundle size • no ES2015 modules = no webpack tree-shaking • Kotlin/JS Dead Code Elimination (DCE)3 ➡ 30kb • performance? 3 https:/ /kotlinlang.org/docs/reference/javascript-dce.html
  63. Javascript Benchmarks Percentile "Real" JS (ms) Kotlin/JS (ms) (ms) P1

    0.068 0.14 +0.072 P50 0.13 0.44 +0.31 P95 0.35 1.03 +0.68 P99 0.67 2.08 +1.41 @ankushg @quizlet
  64. Shared Android Web iOS Grader Everything Else @ankushg @quizlet

  65. Shared Android Web iOS Grader ? Everything Else @ankushg @quizlet

  66. Shared Android Web iOS Grader * Everything Else @ankushg @quizlet

  67. It Could Work

  68. Kotlin All the Way

  69. Shared Android Web iOS Grader * Everything Else @ankushg @quizlet

  70. Shared Android Web iOS Grader * Everything Else * @ankushg

    @quizlet
  71. Shared Android Web iOS Grader * Everything Else * Next-Action

    @ankushg @quizlet
  72. Shared Android Web iOS Grader * Everything Else * Next-Action

    @ankushg @quizlet
  73. It's Only Gotten Better

  74. Gradle Project Structure

  75. The Old Way // mylib-common/build.gradle apply plugin: 'kotlin-platform-common' // mylib-jvm/build.gradle

    apply plugin: 'kotlin-platform-jvm' dependencies { expectedBy project(":mylib-common") }
  76. The Old Way // mylib-common/build.gradle apply plugin: 'kotlin-platform-common' // mylib-jvm/build.gradle

    apply plugin: 'kotlin-platform-jvm' dependencies { expectedBy project(":mylib-common") }
  77. The Old Way // mylib-common/build.gradle apply plugin: 'kotlin-platform-common' // mylib-jvm/build.gradle

    apply plugin: 'kotlin-platform-jvm' dependencies { expectedBy project(":mylib-common") }
  78. The Old Way // mylib-common/build.gradle apply plugin: 'kotlin-platform-common' // mylib-jvm/build.gradle

    apply plugin: 'kotlin-platform-jvm' dependencies { expectedBy project(":mylib-common") } // mylib-js/build.gradle apply plugin: 'kotlin-platform-js' dependencies { expectedBy project(":mylib-common") } // mylib-native/build.gradle apply plugin: 'kotlin-platform-native' dependencies { expectedBy project(":mylib-common") }
  79. The New(er) Way // mylib/build.gradle apply plugin: 'kotlin-multiplatform' kotlin {

    targets { jvm() js() iosx64() } }
  80. The New(er) Way // mylib/build.gradle apply plugin: 'kotlin-multiplatform' kotlin {

    targets { jvm() js() iosx64() } }
  81. The New(er) Way // mylib/build.gradle apply plugin: 'kotlin-multiplatform' kotlin {

    targets { jvm() js() iosx64() } }
  82. Platform Annotations

  83. The Old Way // mylib-common/JvmAnnotations.kt @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.BINARY) expect annotation class

    JvmField() // mylib-jvm/JvmAnnotations.kt actual typealias JvmField = kotlin.jvm.JvmField // mylib-js/JvmAnnotations.kt @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.BINARY) actual annotation class JvmField actual constructor() // NO-OP
  84. The Old Way // mylib-common/JvmAnnotations.kt @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.BINARY) expect annotation class

    JvmField() // mylib-jvm/JvmAnnotations.kt actual typealias JvmField = kotlin.jvm.JvmField // mylib-js/JvmAnnotations.kt @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.BINARY) actual annotation class JvmField actual constructor() // NO-OP
  85. The Old Way // mylib-common/JvmAnnotations.kt @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.BINARY) expect annotation class

    JvmField() // mylib-jvm/JvmAnnotations.kt actual typealias JvmField = kotlin.jvm.JvmField // mylib-js/JvmAnnotations.kt @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.BINARY) actual annotation class JvmField actual constructor() // NO-OP
  86. The New(er) Way @OptionalExpectation @ankushg @quizlet

  87. The New(er) Way @OptionalExpectation • applies to platform-specific annotations @ankushg

    @quizlet
  88. The New(er) Way @OptionalExpectation • applies to platform-specific annotations •

    @JsName, @JvmOverloads, @JvmStatic, @JvmName, etc. @ankushg @quizlet
  89. The New(er) Way @OptionalExpectation • applies to platform-specific annotations •

    @JsName, @JvmOverloads, @JvmStatic, @JvmName, etc. • automatically erases uses on platforms without actual implementationss @ankushg @quizlet
  90. Testing Setup

  91. The Old Way apply plugin: 'com.moowork.node' task installMocha(type: NodeTask) {

    args = [ 'install', yarnDeps.mocha ] }
  92. task jsTestViaMocha( type: NodeTask, dependsOn: [installMocha, compileTestKotlinJs] ) { mocha_path

    = "../node_modules/mocha/bin/mocha" script = file(mocha_path) args = [ file(compileTestKotlinJs.outputFile), ] } jsTest.dependsOn jsTestViaMocha
  93. The New Way apply plugin: 'kotlin-multiplatform' kotlin { js {

    browser() nodejs() } } // downloads and configures test runners!
  94. Wish List @ankushg @quizlet

  95. Wish List • KT-12784: ES2015 support @ankushg @quizlet

  96. Wish List • KT-12784: ES2015 support • KT-25993: Easier way

    to manage DCE exceptions @ankushg @quizlet
  97. Wish List • KT-12784: ES2015 support • KT-25993: Easier way

    to manage DCE exceptions • KT-16604: Generating TypeScript definitions @ankushg @quizlet
  98. Wish List • KT-12784: ES2015 support • KT-25993: Easier way

    to manage DCE exceptions • KT-16604: Generating TypeScript definitions • targetted for Kotlin 1.3.40 @ankushg @quizlet
  99. Now Is the Time

  100. Questions?