Industry: Consumer learning technology
JetBrains products used: Kotlin Multiplatform Mobile
Quizlet is a global learning platform that provides engaging study tools to help people practice and master whatever they are learning.
Every month, over 50 million students, teachers and everyday people use Quizlet to study any subject imaginable for school, work or as part of their personal interests. Combining cognitive science and machine learning, Quizlet guides students through adaptive study activities to confidently reach their learning goals.
Quizlet is used in over 130 countries and is available in 19 languages. As the largest user-generated learning platform, people can choose from over 400 million study sets on Quizlet or create their own, and immediately begin studying across Quizlet’s activities for free. In fact, over a billion questions are answered on Quizlet each week.
Our Android app has over 10 million active installations as per the Google Play Store. Our iOS app is a top 10 Education app on the App Store.
Why Kotlin — Options for Shared Code
To power the unique use cases for our rapidly growing userbase, we had to go beyond simply querying a database, throwing things into a UI, picking a random element, and using String-comparisons to assess if a user answered a question correctly.
Quizlet began writing more advanced code:
- standardized analytics events to help track learning outcomes
- a context-dependent grading rule engine to go beyond simple string comparisons
- modeling the user’s brain to generate questions designed to help them retain information better
What were the Alternatives?
- Since none of the shared code was being run in the standard mobile runtime, debuggability was also an issue once crashes inevitably occurred.
- On Android, the J2V8 library caused our APK size to almost double
All-told, these issues resulted in an ecosystem where frontend web developers might have felt did not feel comfortable consuming it (let alone writing it themselves).
We explored React Native, but were more constrained by rewriting our complex business logic than we were on being able to iterate rapidly on user interfaces. The tradeoffs to user and developer experience weren’t worth it.
We also explored shared code via C++/Rust/Go, but had already ran into debugging and marshalling issues with the JNI on Android with J2V8, and most of our frontend web code is written assuming this logic runs client-side which becomes tricky with these technologies.
Deciding on Kotlin Multiplatform
What caught our attention was how Kotlin Multiplatform’s unique approach addresses many of the issues we had with the other methods of sharing code that we explored.
- developer satisfaction
Though it was excellent to see that Kotlin Multiplatform has well-supported networking, persistence, and serialization libraries, none of these were necessary to support Quizlet’s shared business logic.
As mentioned earlier, the shared code interop area traditionally relies on manual type declarations, loss of type-safety, and a considerable performance hit when marshaling between types on mobile clients. Kotlin Multiplatform on the other hand, generates type-safe/null-safe code for our mobile clients. Our Android client can treat Kotlin Multiplatform code the same way it treats all Kotlin code. Our iOS client can safely create instances of Kotlin classes as if they were written in Objective-C.
- 1.5s off of J2V8 initialization time,
- 50x faster performance when calling into the shared code due to bypassing data marshalling
- 5x faster runtime of shared code itself
APK size on Android dropped from 18MB to 10MB once we were able to remove the J2V8 runtime. Web bundle size increased by 30Kb after dead-code-elimination was applied, but that was acceptable.
Furthermore, Kotlin is a modern language, with established tooling, and great IDE support: It is after all, designed by an IDE company!
- Increasing Kotlin knowledge across our engineering organization (less of an issue now that we are also using Kotlin for backend services!)
- Developing workflows to publish, consume, and debug iOS and Web artifacts
- Missing Typescript definitions for enums in Kotlin 1.4’s new JS IR (although JetBrains is working on this!)
- Having to pass through Objective-C to consume from Swift means that we lose access to features that both Kotlin and Swift support like “real” enums
- iOS exception handling is a little finicky because it requires manual handling
Clean interfaces are crucial.
Writing code with clear interface boundaries made it much easier to extract later. By isolating our vital business logic from regular application code, we were able to share this logic across applications.
Aggressively validate inputs along the public API.
Practice Test-Driven Development.
Test Driven Development (TDD) pays extra dividends with shared code. TDD is nearly always a great way to build software, but it is especially well-suited for shared modules with little dependence on external state.
Particularly with shared code, investing in TDD:
- Minimizes time spent debugging the final artifact within a host client, which tends to be more difficult than debugging purely native code.
- Minimizes the number of times we have to have to recompile/repackage shared code for inclusion into a client due to implementation errors. This multi-step processes can be awkward and takes more time than working entirely in a native environment.
- Gives us extra confidence in the shared code we write, preventing issues that have ripple effects across multiple host apps.
Complex state machines and rule engines are ideal candidates.
Compared to user interfaces, persistence, or networking, state machines and rule engines are incredibly well-suited for shared code. This isn’t to say networking and persistence are bad candidates for shared code: they just have additional complications to work around.
By focusing our shared code efforts on code based around state management and control flow, we saved our engineering team countless person-hours with minimal time spent on cross-platform threading or concurrency concerns.
Ankush Gupta, Staff Software Engineer, Quizlet
Contacts and Links
- Our blog post: Shared Code at Quizlet: Deciding on Kotlin Multiplatform
- Corresponding talk at droidcon San Francisco 2019: Powering Worldwide Learning with Kotlin Multiplatform
Director of Engineering — firstname.lastname@example.org
Yandex uses Kotlin Multiplatform Mobile in their Disk and Maps apps to share the business logic between iOS and Android apps and wrap the existing cross-platform C++ library.
Mirego is an end-to-end digital product team. They build digital ecosystems that allow their clients to elevate customers’ or employees’ experience. Kotlin Multiplatform Mobile is the go-to framework for mobile development in Mirego.