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

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!

Ankush Gupta

July 03, 2019
Tweet

Other Decks in Programming

Transcript

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

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

    mobile apps... • tens of millions of users joined the website @ankushg @quizlet
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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 ✅
  11. 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
  12. 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
  13. 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
  14. Enter Shared Javascript • In-browser on Web • JavascriptCore on

    iOS • J2V8 on Android2 2 Damien Diehl - Comparison Shopping: Searching for JavaScript Engines for Android
  15. Not without its problems • performance • APK size •

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

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

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

    stability • debuggability • developer confidence But it got the job done... for years. @ankushg @quizlet
  19. Is It Actually Viable? • performance • bundle size •

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

    stability • debuggability • developer confidence @ankushg @quizlet
  21. Compilation ≠ Correctness • kotlin.test to port existing test suite

    • BigQuery UDFs to validate with real-world data @ankushg @quizlet
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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") }
  27. 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") }
  28. 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") }
  29. 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") }
  30. 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
  31. 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
  32. 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
  33. The New(er) Way @OptionalExpectation • applies to platform-specific annotations •

    @JsName, @JvmOverloads, @JvmStatic, @JvmName, etc. @ankushg @quizlet
  34. 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
  35. 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
  36. The New Way apply plugin: 'kotlin-multiplatform' kotlin { js {

    browser() nodejs() } } // downloads and configures test runners!
  37. Wish List • KT-12784: ES2015 support • KT-25993: Easier way

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

    to manage DCE exceptions • KT-16604: Generating TypeScript definitions @ankushg @quizlet
  39. 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