$30 off During Our Annual Pro Sale. View Details »

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 Journey in Shared
    Code with Kotlin
    Multiplatform
    @ankushg
    @quizlet

    View Slide

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

    View Slide

  3. A Brief History of Quizlet
    @ankushg
    @quizlet

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 ✅

    View Slide

  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

    View Slide

  18. Android was
    stuck playing
    catch-up

    View Slide

  19. Slow down or
    double-down?

    View Slide

  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

    View Slide

  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

    View Slide

  22. How We
    Did It

    View Slide

  23. Enter Shared
    Javascript

    View Slide

  24. Enter Shared
    Javascript
    • In-browser on Web

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  29. Not without its problems
    @ankushg
    @quizlet

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. A new challenger
    approaches...

    View Slide

  38. A new challenger
    approaches...
    Kotlin
    Multiplatform

    View Slide

  39. View Slide

  40. Rethinking
    Shared Code

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. Is It Actually Viable?
    @ankushg
    @quizlet

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  51. Shared Android Web iOS
    Grader
    Everything
    Else
    @ankushg
    @quizlet

    View Slide

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

    View Slide

  53. Compilation ≠ Correctness
    @ankushg
    @quizlet

    View Slide

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

    View Slide

  55. Compilation ≠ Correctness
    • kotlin.test to port existing test suite
    • BigQuery UDFs to validate with real-world data
    @ankushg
    @quizlet

    View Slide

  56. Shared Android Web iOS
    Grader
    Everything
    Else
    @ankushg
    @quizlet

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  64. Shared Android Web iOS
    Grader
    Everything
    Else
    @ankushg
    @quizlet

    View Slide

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

    View Slide

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

    View Slide

  67. It Could
    Work

    View Slide

  68. Kotlin
    All the Way

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  73. It's Only
    Gotten
    Better

    View Slide

  74. Gradle
    Project
    Structure

    View Slide

  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")
    }

    View Slide

  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")
    }

    View Slide

  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")
    }

    View Slide

  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")
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  82. Platform
    Annotations

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  86. The New(er) Way
    @OptionalExpectation
    @ankushg
    @quizlet

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  90. Testing
    Setup

    View Slide

  91. The Old Way
    apply plugin: 'com.moowork.node'
    task installMocha(type: NodeTask) {
    args = [
    'install',
    yarnDeps.mocha
    ]
    }

    View Slide

  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

    View Slide

  93. The New Way
    apply plugin: 'kotlin-multiplatform'
    kotlin {
    js {
    browser()
    nodejs()
    }
    }
    // downloads and configures test runners!

    View Slide

  94. Wish List
    @ankushg
    @quizlet

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  99. Now Is
    the Time

    View Slide

  100. Questions?

    View Slide