<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ankushg.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ankushg.com/" rel="alternate" type="text/html" /><updated>2025-07-20T09:40:14-07:00</updated><id>https://ankushg.com/feed.xml</id><title type="html">Ankush Gupta</title><subtitle>Trials and tribulations of Ankush, the Software Engineer.</subtitle><author><name>Ankush Gupta</name></author><entry><title type="html">Write Once, Run Anywhere?</title><link href="https://ankushg.com/posts/write-once-run-anywhere-increment/" rel="alternate" type="text/html" title="Write Once, Run Anywhere?" /><published>2021-08-31T00:00:00-07:00</published><updated>2021-08-31T00:00:00-07:00</updated><id>https://ankushg.com/posts/write-once-run-anywhere-increment</id><content type="html" xml:base="https://ankushg.com/posts/write-once-run-anywhere-increment/"><![CDATA[]]></content><author><name>Ankush Gupta</name></author><category term="posts" /><category term="publications" /><category term="kotlin" /><category term="mobile" /><category term="multiplatform" /><category term="software" /><summary type="html"><![CDATA[I wrote an [article](https://increment.com/mobile/write-once-run-anywhere/) for [Increment's Mobile edition](https://increment.com/mobile/), sharing lessons about the nuances of cross-platform development. Published in print and digital!]]></summary></entry><entry><title type="html">Quizlet + Kotlin Multiplatform Case Study</title><link href="https://ankushg.com/posts/quizlet-kotlin-multiplatform-case-study/" rel="alternate" type="text/html" title="Quizlet + Kotlin Multiplatform Case Study" /><published>2020-08-31T00:00:00-07:00</published><updated>2020-08-31T00:00:00-07:00</updated><id>https://ankushg.com/posts/quizlet-kotlin-multiplatform-case-study</id><content type="html" xml:base="https://ankushg.com/posts/quizlet-kotlin-multiplatform-case-study/"><![CDATA[]]></content><author><name>Ankush Gupta</name></author><category term="posts" /><category term="publications" /><category term="kotlin" /><category term="mobile" /><category term="multiplatform" /><category term="software" /><summary type="html"><![CDATA[I authored a [case study](https://kotlinlang.org/lp/mobile/case-studies/quizlet/) with JetBrains to share how Quizlet is using Kotlin Multiplatform to simplify our development efforts.]]></summary></entry><entry><title type="html">Shared Code at Quizlet: Deciding on Kotlin Multiplatform</title><link href="https://ankushg.com/posts/shared-code-kotlin-multiplatform/" rel="alternate" type="text/html" title="Shared Code at Quizlet: Deciding on Kotlin Multiplatform" /><published>2019-11-27T00:00:00-08:00</published><updated>2019-11-27T00:00:00-08:00</updated><id>https://ankushg.com/posts/shared-code-kotlin-multiplatform</id><content type="html" xml:base="https://ankushg.com/posts/shared-code-kotlin-multiplatform/"><![CDATA[<p>For readers who don’t know about us already, <a href="https://quizlet.com/careers">Quizlet</a>’s mission is to help our users practice and master whatever they’re trying to learn. We primarily do this by building tools for learners to create, study, and share content, with more than 50 million students and teachers using our product each month.</p>

<p>In this article, we’ll share the details of:</p>

<ul>
  <li><strong>How we came to the conclusion</strong> that we should invest in shared code</li>
  <li><strong>Different industry approaches</strong> to shared code and their limitations</li>
  <li><strong>Lessons we learned</strong> from our first attempt at shared code with <strong>JavaScript</strong></li>
  <li>Why we decided to rewrite our shared code in <strong>Kotlin Multiplatform</strong></li>
</ul>

<p>Like many startups, Quizlet began as a product for a single platform: the web. Our core product offering was essentially user-generated digital flashcards, and was originally created to master vocabulary.</p>

<p>In order to “assess” a user’s mastery over a specific element, we would simply pick at random from the list a user was studying, ask them to type in the answer, and perform a string equality check between the user’s submission and what the database said the correct answer was. If we were feeling generous, we might ignore case or punctuation.</p>

<p>These were simple times.</p>

<p>As the years progressed, word of Quizlet spread from student to student, teacher to teacher. We were being used by:</p>

<ul>
  <li><strong>tens of millions</strong> of users</li>
  <li>who were creating <strong>millions</strong> of sets of study material</li>
  <li>which spanned <strong>thousands</strong> of different subjects</li>
  <li>with demand to be able to study things across <strong>several</strong> different modalities</li>
</ul>

<p>Things were starting to get complicated!</p>

<h2 id="quizlets-secret-sauce-">Quizlet’s Secret Sauce 🍝</h2>

<p>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 correctness.</p>

<p>Quizlet needed to be smarter.</p>

<p>We began writing extremely specialized, domain-specific logic to help our users learn more effectively. Some examples of this kind of business logic include:</p>

<ul>
  <li>standardized <strong>analytics events</strong> to help track learning outcomes</li>
  <li>a context-dependent <strong>grading rule engine</strong> to go beyond simple string comparisons</li>
  <li><a href="https://medium.com/tech-quizlet/spaced-repetition-for-all-cognitive-science-meets-big-data-in-a-procrastinating-world-59e4d2c8ede1">modeling the user’s <strong>brain</strong> to help them retain information better</a></li>
</ul>

<p>In visual terms, our website went from this:</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/stage1-464-832a1ce27.png" srcset="/generated/assets/images/quizlet-shared-kotlin/stage1-450-cbf1f306e.webp 450w, /generated/assets/images/quizlet-shared-kotlin/stage1-464-cbf1f306e.webp 464w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="464" height="239" />
<figcaption>
      Simple Web Client + API

    </figcaption></figure>

<p>to this:</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/stage2-800-e25c5e3a2.png" srcset="/generated/assets/images/quizlet-shared-kotlin/stage2-450-51bc98515.webp 450w, /generated/assets/images/quizlet-shared-kotlin/stage2-650-51bc98515.webp 650w, /generated/assets/images/quizlet-shared-kotlin/stage2-850-51bc98515.webp 850w, /generated/assets/images/quizlet-shared-kotlin/stage2-1000-51bc98515.webp 1000w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="1158" height="891" />
<figcaption>
      Complex Web Client + API

    </figcaption></figure>

<p>Each of these pieces of code are tied to Quizlet’s mission to make education better and more accessible. Each of these components <strong>also</strong> requires a deep, domain-specific understanding of our specific product and market, well beyond what it takes to write a generic web application.</p>

<p>This category of code is what we will refer to as a business’s “<strong>Secret Sauce</strong>.”</p>

<p>The Secret Sauce elevates an app from a simple view into a database to a product that delivers delightful experiences and results. The Secret Sauce is directly responsible for both empowering users, and outperforming competitors.</p>

<p>While it’s easy to infer that continued investments in the Secret Sauce have outsized impacts on a product, Quizlet was reaching a phase where that was easier said than done.</p>

<p>The web team had years to implement the fundamentals of the website before finding the need to dive deep into the Secret Sauce. But our fledgling mobile teams were still trying to reach parity on even the most basic features.</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/stage3-800-aae265137.png" srcset="/generated/assets/images/quizlet-shared-kotlin/stage3-450-e4429f3dd.webp 450w, /generated/assets/images/quizlet-shared-kotlin/stage3-650-e4429f3dd.webp 650w, /generated/assets/images/quizlet-shared-kotlin/stage3-850-e4429f3dd.webp 850w, /generated/assets/images/quizlet-shared-kotlin/stage3-1000-e4429f3dd.webp 1000w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="1701" height="1247" />
<figcaption>
      Complex Web Client… Brand-New Mobile Clients

    </figcaption></figure>

<p>It’s hard enough to write niche, complicated, domain-specific business logic and get it right once. Writing it precisely the same on <strong>three platforms</strong>, where two of them were starting from scratch, and also had to deal with persistence and networking concerns?</p>

<p>This was proving to be a nightmare.</p>

<p>If we wanted to continue to make Quizlet magical, we had to address this problem head-on.</p>

<p>But how?</p>

<h3 id="stop-">Stop? 🛑</h3>

<p>One option was to declare that <strong>parity across clients is impossible</strong> with this category of features.</p>

<p>The most literal interpretation of this would mean that we simply wouldn’t support advanced features on our clients. We could just stop rewriting this fancy logic on non-web platforms and cut our losses.</p>

<p>The more likely situation would have been that we moved to an API-side implementation of our secret sauce instead of performing those operations on-device.</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/stage4-800-59564df23.png" srcset="/generated/assets/images/quizlet-shared-kotlin/stage4-450-d832364a3.webp 450w, /generated/assets/images/quizlet-shared-kotlin/stage4-650-d832364a3.webp 650w, /generated/assets/images/quizlet-shared-kotlin/stage4-850-d832364a3.webp 850w, /generated/assets/images/quizlet-shared-kotlin/stage4-868-d832364a3.webp 868w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="868" height="750" />
<figcaption>
      Simple Web and Mobile Clients with Secret Sauce behind the API

    </figcaption></figure>

<p>For many businesses, requiring internet connectivity for core product features might be a very reasonable requirement! Quizlet however has a particularly strong affinity for mobile users with limited Internet access.</p>

<p>Younger users and learners in developing countries tend to use mobile devices more than desktop computers. These same users also tend to have less reliable access to the Internet, either due to data caps or connectivity issues.</p>

<p>Furthermore, the components of Quizlet’s secret sauce that we would need to move behind the API are all extremely central to the user experience.</p>

<p>If we</p>

<ul>
  <li>only had to call out to the server once per session,</li>
  <li>could do so in the background without blocking the user from proceeding,</li>
  <li>could cache the result, and <em>also</em></li>
  <li>gracefully fall back if the server could not be reached…</li>
</ul>

<p>then maybe waiting a second or two for a response might be fine.</p>

<p>However, our users rely on our Secret Sauce every few seconds to respond to user input during core studying flows. Taking even a <em>second</em> per request over a slow connection would result in an unacceptable user experience. As a result, this wasn’t a trade-off we were able to justify.</p>

<h3 id="slow-down-">Slow Down? 🚧</h3>

<p>Another option at our disposal was to <strong>slow down the pace</strong> at which we were writing advanced features that push the boundaries in learning.</p>

<p>This would give our mobile teams the breathing room to write an implementation of our Secret Sauce for each platform. In this world, we may have also been able to write test cases in some declarative way that all three platforms could validate against.</p>

<p>While taking a breather is always an option, maintaining a high velocity of feature development is what allowed Quizlet to get to where we are today. We understandably didn’t want to feature-freeze our product.</p>

<h3 id="double-down">Double Down!</h3>

<p>The final option was to <strong>invest heavily in our ability to ship such features across platforms</strong> and double down on our Secret Sauce. For some companies, this might just mean hiring a ton of Android and iOS engineers to rewrite the secret sauce onto their platforms.</p>

<p>The approach that caught <em>our</em> eye however, was investing in our ability to write domain-specific code once, and <strong>share it across platforms</strong>.</p>

<p>Cross-platform shared code is far from a novel idea that we came up with at Quizlet. Several other companies have attempted it before us, in many different ways, with varying degrees of success.</p>

<p>Several other companies will do it after us and hopefully learn from our experiences.</p>

<h2 id="approaches-to-shared-code-in-industry">Approaches to Shared Code in Industry</h2>

<p>Here’s a <em>very, very brief</em> summary of approaches, technologies, and learnings on shared code from various companies.</p>

<h3 id="share-all-the-things">Share All the Things!</h3>

<p><strong>Promise</strong>: Write your entire app once and run it everywhere</p>

<p><strong>Industry Examples</strong>: React Native at Airbnb, Pinterest, Nextdoor</p>

<p><strong>Realities</strong></p>

<ul>
  <li>Heavy reliance on bridging infrastructure</li>
  <li>Difficult to seamlessly integrate with existing native apps</li>
  <li>Apps may have performance issues due to differences in threading models</li>
  <li>UI can feel non-native</li>
  <li>Less mature frameworks/libraries than native code</li>
  <li>Difficult to integrate deeply with the OS when you need to do so</li>
</ul>

<p>Unfortunately, many of the large companies that were pioneering React Native <a href="https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a">ended up discontinuing their use of it</a>, for both organizational and technical reasons.</p>

<blockquote>
  <p>Every company that I know of that has tried this hybrid approach has walked it back months later. . . .
The companies that seem successful with React Native seem to be the ones that have their entire app in RN and started out the app that way. . . .
We’re still in the process of winding down RN here at Nextdoor.
— <a href="https://medium.com/@vivekxk/every-company-that-i-know-of-that-has-tried-this-hybrid-approach-has-walked-it-back-months-later-b00678b68f4b">Vivek Karuturi (Nextdoor)</a></p>
</blockquote>

<h3 id="share-persistence-and-networking-things">Share Persistence and Networking Things!</h3>

<p>Many issues from the previous approach stem from trying to share UI implementation, and from using JavaScript in performance-sensitive environments.</p>

<p>A few companies have tried to side-step these issue by sharing non-UI code. This code may rely on persistence and networking, and is typically written in highly performant languages such as C++.</p>

<p><strong>Promise</strong>: Have a native UI and write <em>non-UI</em> code only once</p>

<p><strong>Industry Examples</strong>: C++ at Dropbox, Slack, <a href="https://medium.com/safetycultureengineering/how-to-build-a-shared-c-library-for-ios-and-android-a3817aba5798">SafetyCulture</a></p>

<p><strong>Realities</strong>:</p>

<ul>
  <li>Issues with hiring/retention due to niche/custom tooling and languages</li>
  <li>Persistence and networking requires strong concurrency support</li>
  <li>Potential performance overhead with JNI on Android</li>
  <li>Much more difficult to share code with frontend web clients</li>
  <li>Loss of types when crossing the module boundary</li>
  <li>Some functionality simply works <em>differently</em> on different platforms</li>
</ul>

<p>An industry example of this line of thinking is Dropbox. They wrote shared code for features such as syncing the camera roll on mobile devices, which introduces a dependency on networking and storage access. Dropbox Engineering recently <a href="https://blogs.dropbox.com/tech/2019/08/the-not-so-hidden-cost-of-sharing-code-between-ios-and-android/">posted about their experiences</a> with shared C++ and announced that they stopped using it.</p>

<blockquote>
  <p>Although writing code once sounds like a great bargain, the associated overhead . . . outweigh[s] the benefits (which turned out to be smaller than expected anyway).
In the end we no longer share mobile code via C++ . . . and instead write code in the platform native languages.
—<a href="https://blogs.dropbox.com/tech/2019/08/the-not-so-hidden-cost-of-sharing-code-between-ios-and-android/">Eyal Guthmann (Dropbox)</a></p>
</blockquote>

<p>Slack also followed up with <a href="https://slack.engineering/client-consistency-at-slack-beyond-libslack-c9cfbe778fb7">a similar post,</a> where they announced that they no longer use shared C++. Instead, they have switched gears to invest in engineering processes to maintain consistency across clients.</p>

<blockquote>
  <p>While we are no longer building a shared library, Slack still needs to maintain consistency and reduce duplication of effort, while developing separate implementations of client infrastructure.
— <a href="https://slack.engineering/client-consistency-at-slack-beyond-libslack-c9cfbe778fb7">Tracy Stampfli (Slack)</a></p>
</blockquote>

<h3 id="share-the-secret-sauce-">Share the Secret Sauce 🍝?</h3>

<p>Common threads with the previously described approaches were the reliance on cross-platform abstractions for UI, persistence, networking, and/or serialization.</p>

<p>These concepts are hard to generalize across multiple platforms. As a result, we posit that frameworks attempting to do so tend to either:</p>

<ul>
  <li><strong>oversimplify</strong> to the point of having to <em>bypass</em> the framework to accomplish key tasks, or</li>
  <li><strong>overcomplicate</strong> to the point of being too much of a hassle to work with in a productive manner</li>
</ul>

<p>It would undeniably be <em>nice</em> if we could write our entire codebase once and share it everywhere. However,  any given engineer at Quizlet already knows how to deal with UI, persistence, and networking concepts on their respective platforms.</p>

<p>Knowing ins-and-outs of German grammar rules to maintain grading logic, or keeping up with cognitive science research to model a user’s ability to remember information are much more difficult tasks.</p>

<p><em>Unlike companies like Airbnb</em>, we had to implement our secret sauce on-device, so we had more pressing demands than iterating quickly on UI.</p>

<p><em>Unlike companies like Dropbox or Slack</em>, our secret sauce <em>doesn’t</em> rely on the fine-grained details of network or disk I/O.</p>

<p>At Quizlet, we had a clear sweet spot at the intersection of <em>our</em> Secret Sauce and the areas where shared code has minimal complications.</p>

<p>So how did we do it?</p>

<h2 id="attempt-1-shared-javascript">Attempt 1: Shared JavaScript</h2>

<p>Our first attempt to share code was an attempt to reuse what we already had.</p>

<p>The Quizlet Secret Sauce was originally written in JavaScript to target our web client. The challenge was getting it to run on mobile.</p>

<p>With a bit of initial setup, we were able to clean up the existing web code along our class boundaries and export a suite of small libraries. Each library was responsible for a distinct portion of our Secret Sauce business logic.</p>

<p>We could then bundle these files into our mobile apps and run them. The end-result was to read each library file into a String in-memory and pass it into a JavaScript runtime.</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/stage5-800-2f41e9417.png" srcset="/generated/assets/images/quizlet-shared-kotlin/stage5-450-9b4f2fe2e.webp 450w, /generated/assets/images/quizlet-shared-kotlin/stage5-650-9b4f2fe2e.webp 650w, /generated/assets/images/quizlet-shared-kotlin/stage5-850-9b4f2fe2e.webp 850w, /generated/assets/images/quizlet-shared-kotlin/stage5-1000-9b4f2fe2e.webp 1000w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="1150" height="751" />
<figcaption>
      JS Artifacts in Mobile Clients

    </figcaption></figure>

<p>In order to interact with this code, we would marshal “real” iOS or Android classes into JS-friendly versions, and build a string that ran a given function from the JS runtime. We would then marshal the output strings back to “real” classes.</p>

<p>This was relatively straightforward to throw together on iOS using an official Apple framework called <a href="https://developer.apple.com/documentation/javascriptcore">JavaScriptCore</a>. JavaScriptCore powers Safari on iOS, is maintained by Apple, and behaves consistently across devices.</p>

<p>Initially, we attempted the same approach on Android using the official <a href="https://developer.android.com/reference/android/webkit/WebView.html#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E)">WebView::evaluateJavascript</a> API. With this approach, we ran into several issues with performance, stability, and variance across manufacturer implementations. <a href="https://medium.com/tech-quizlet/comparison-shopping-searching-for-javascript-engines-for-android-bdc656538f2e">In a blog post from 2016</a>, we detail how we evaluated several external third-party JavaScript engines before deciding on the J2V8 library for Android.</p>

<h3 id="far-from-problem-free">Far from Problem-Free</h3>

<p>Our experiences sharing JavaScript on native mobile were shaky.</p>

<p>While web browsers are of course tailor-made to run JavaScript, relying on JavaScriptCore/J2V8 on iOS/Android raised significant issues.</p>

<p>Relying on disk I/O to load the initial JS files, building and retaining a reference to an entire JavaScript context, and then having to marshal “real” objects to/from JavaScript in order to interact with  the code resulted in huge <strong>performance</strong> issues and <strong>memory leaks</strong>.</p>

<p>Unlike actual native code, interacting with untyped objects that are passed to/from JavaScript contexts was disastrously <strong>error-prone</strong>, without any compile-time safety.</p>

<p>Since none of the shared code was being run in a first-class mobile runtime, <strong>debuggability</strong> was also an issue once crashes inevitably occurred.</p>

<p>On Android, the J2V8 library caused our <strong>APK size</strong> to almost <em>double</em>.</p>

<p>And this is all before even mentioning the almost comical <strong>disdain for JavaScript</strong> among mobile developers 😱.</p>

<p>All-told, these issues resulted in an ecosystem where frontend web developers might have felt comfortable writing shared code, but mobile developers certainly did not feel comfortable consuming it.</p>

<h3 id="but-it-worked">But It Worked</h3>

<p>Problems aside, sharing our Secret Sauce through JavaScript <em>worked. For years!</em></p>

<p>As much fun as people have hating on JavaScript, just getting to this point was an enormous win for Quizlet.</p>

<p>We were able to write our most critical business logic in one place, ship it across multiple platforms, and unblock our resource-constrained native mobile teams. Most importantly, we were able to do this without committing to writing our entire client with the same framework.</p>

<p>Without shared code, we would not have been able to get our award-winning <a href="https://quizlet.app.link/VyYD1ywNYR">Android</a> and <a href="https://quizlet.app.link/V5hndX6FYR">iOS</a> clients to where they are today.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<p>Through our experiences with Shared JavaScript, we learned several lessons.</p>

<h3 id="clean-interfaces-are-crucial"><strong>Clean interfaces are crucial.</strong></h3>

<p>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!</p>

<h3 id="aggressively-validate-inputs-along-the-public-api"><strong>Aggressively validate inputs along the public API.</strong></h3>

<p>It’s easy for misunderstandings about the meanings of parameters to arise in a complex module. This is doubly true when the module is used by many people who may have never talked directly to each other. Aggressively validating for unexpected input, especially in a loosely-typed language like JavaScript, helps minimize issues due to miscommunication or under-documentation.</p>

<h3 id="practice-test-driven-development"><strong>Practice Test-Driven Development.</strong></h3>

<p>Test Driven Development (TDD) pays extra dividends with shared code. TDD is nearly always a great way to build software, but it is <em>especially</em> well-suited for shared modules with little dependence on external state.</p>

<p>Particularly with shared code, investing in TDD:</p>

<ul>
  <li>Minimizes time spent debugging the final artifact within a host client, which tends to be more difficult than debugging purely native code.</li>
  <li>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.</li>
  <li>Gives us extra confidence in the shared code we write, preventing issues that have ripple effects across multiple host apps.</li>
</ul>

<h3 id="complex-state-machines-and-rule-engines-are-ideal-candidates"><strong>Complex state machines and rule engines are ideal candidates.</strong></h3>

<p>Compared to user interfaces, persistence, or networking, state machines and rule engines are well-suited for shared code. This isn’t to say networking and persistence are <em>bad</em> candidates for shared code: they just have additional complications to work around.</p>

<p>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.</p>

<h2 id="enter-kotlin-multiplatform">Enter Kotlin Multiplatform</h2>

<p><a href="https://kotlinlang.org/">Kotlin</a> is a programming language created by <a href="https://www.jetbrains.com/">JetBrains</a>. It was designed to run in the JVM and has excellent interoperability with Java. Once it took off within the Android development community, it also received backing from Google, through the <a href="https://kotlinlang.org/foundation/kotlin-foundation.html">Kotlin Foundation</a>.</p>

<p>It’s only been getting more popular ever since!</p>

<p><strong>Kotlin Multiplatform</strong> was announced at KotlinConf 2017. The basic promise of Kotlin Multiplatform was to bring the same magic of Kotlin’s excellent JVM interop to other runtime targets and platforms.</p>

<p>By building out separate compiler backends for different targets, Kotlin Multiplatform compiles platform-agnostic Kotlin code (aka “Common” code) into many different artifacts.</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/kmpp-model-800-0107c8312.png" srcset="/generated/assets/images/quizlet-shared-kotlin/kmpp-model-450-ace5ddf1a.webp 450w, /generated/assets/images/quizlet-shared-kotlin/kmpp-model-650-ace5ddf1a.webp 650w, /generated/assets/images/quizlet-shared-kotlin/kmpp-model-850-ace5ddf1a.webp 850w, /generated/assets/images/quizlet-shared-kotlin/kmpp-model-1000-ace5ddf1a.webp 1000w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="2117" height="825" />
<figcaption>
      Kotlin Multiplatform’s Compiler Framework

    </figcaption></figure>

<p>One of the core features of Kotlin Multiplatform is the ability to write <a href="https://kotlinlang.org/docs/reference/platform-specific-declarations.html">platform-specific declarations</a>. This allows Kotlin Common code to define a class or function as expected, and then continue to use it within Common code.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Common</span>
<span class="n">expect</span> <span class="k">fun</span> <span class="nf">platformName</span><span class="p">():</span> <span class="nc">String</span>

<span class="k">fun</span> <span class="nf">getGreeting</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s">"Kotlin is running on ${platformName()}!"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Each platform-specific sourceset is then required to provide an actual implementation for every expected declaration. The beauty here is that platform-specific sourcesets are not restricted to Kotlin Common. Instead, they are free to use every API available to that platform!</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Android</span>
<span class="n">actual</span> <span class="k">fun</span> <span class="nf">platformName</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"Android"</span>

<span class="c1">// iOS</span>
<span class="n">actual</span> <span class="k">fun</span> <span class="nf">platformName</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nc">UIDevice</span><span class="p">.</span><span class="n">currentDevice</span><span class="p">.</span><span class="nf">systemName</span><span class="p">()</span> <span class="p">+</span> <span class="s">" "</span> <span class="p">+</span>
            <span class="nc">UIDevice</span><span class="p">.</span><span class="n">currentDevice</span><span class="p">.</span><span class="n">systemVersion</span>
<span class="p">}</span>

<span class="c1">// JS</span>
<span class="n">actual</span> <span class="k">fun</span> <span class="nf">platformName</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nc">Navigator</span><span class="p">.</span><span class="n">userAgent</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Though it is excellent to see that Kotlin Multiplatform has well-supported <a href="https://ktor.io/">networking</a>, <a href="https://github.com/cashapp/sqldelight">persistence</a>, and <a href="https://github.com/Kotlin/kotlinx.serialization">serialization</a> libraries, none of these were  even necessary to support Quizlet’s shared code.</p>

<p>What caught our attention was how Kotlin Multiplatform’s unique approach addresses many of the issues we had with sharing JavaScript across platforms.</p>

<p>Namely:</p>

<ul>
  <li>performance,</li>
  <li>error-proneness, and</li>
  <li>developer satisfaction.</li>
</ul>

<p>By generating actual Objective-C Frameworks, JavaScript files, and Java bytecode, Kotlin Multiplatform promises the ability to write code in Kotlin and have it <strong>run as a first-class citizen on each platform</strong>.</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/quizlet-shared-kotlin/stage6-800-f47e628fb.png" srcset="/generated/assets/images/quizlet-shared-kotlin/stage6-450-5cdbeed57.webp 450w, /generated/assets/images/quizlet-shared-kotlin/stage6-650-5cdbeed57.webp 650w, /generated/assets/images/quizlet-shared-kotlin/stage6-850-5cdbeed57.webp 850w, /generated/assets/images/quizlet-shared-kotlin/stage6-1000-5cdbeed57.webp 1000w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="1128" height="831" />
<figcaption>
      Kotlin Multiplatform’s Promised Interaction

    </figcaption></figure>

<p>This approach eliminates the most problematic areas of our Shared JavaScript approach: the bridge layer and external runtime requirement. While other technologies (such as shared C++, Rust, or Go) might have foregone the external runtime requirement, the they still rely on bridging technologies.</p>

<p>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 <strong>create instances of Kotlin classes as if they were written in Objective-C</strong>.</p>

<p>Furthermore, <strong>Kotlin is a modern language</strong>, with established tooling, and great IDE support: It is after all, designed by an IDE company!</p>

<p>Quizlet’s Android, iOS, and backend engineers are more eager to write and maintain code written in Kotlin rather than JavaScript. After playing around with the interactive <a href="https://play.kotlinlang.org/byExample/overview">“Kotlin by Example” section on the Kotlin website</a>, <strong>even our frontend web engineers found themselves impressed by Kotlin</strong>.</p>

<p>Kotlin Multiplatform addresses all of our pain points with Shared JavaScript on mobile clients. In early 2019, Quizlet migrated all of our shared code over to Kotlin Multiplatform, and we are using it in production to serve over 50 million monthly active users.</p>

<p>Was the process perfect and painless? Of course not! Some issues we had to navigate include:</p>

<ul>
  <li>Increasing Kotlin knowledge across our engineering organization</li>
  <li>Developing workflows to publish, consume, and debug iOS and Web artifacts</li>
  <li>Not having Typescript definitions for JS clients (<a href="https://youtrack.jetbrains.com/issue/KT-16604">although JetBrains is working on this</a>!)</li>
  <li>Navigating differences in Kotlin types on <a href="https://kotlinlang.org/docs/reference/js-to-kotlin-interop.html#representing-kotlin-types-in-javascript">JavaScript</a> and <a href="https://kotlinlang.org/docs/reference/native/objc_interop.html#mappings">iOS</a> when validating inputs</li>
</ul>

<p>But was it worth it? <strong>Yes it was!</strong></p>

<p>In the future, we’ll continue to share more detailed benchmarks and specifics about our experience migrating our shared libraries from JavaScript to Kotlin Multiplatform.</p>

<p>Keep an eye out for future posts!</p>]]></content><author><name>Ankush Gupta</name></author><category term="posts" /><category term="kotlin" /><category term="mobile" /><category term="multiplatform" /><category term="software" /><summary type="html"><![CDATA[Learn how Quizlet decided to invest in shared code, how different industry approaches compare, lessons learned along the way, and why we decided on [Kotlin Multiplatform](https://www.jetbrains.com/lp/mobilecrossplatform/).]]></summary></entry><entry><title type="html">Powering Worldwide Learning with Kotlin Multiplatform</title><link href="https://ankushg.com/speaking/droidcon-sf-2019" rel="alternate" type="text/html" title="Powering Worldwide Learning with Kotlin Multiplatform" /><published>2019-11-26T00:00:00-08:00</published><updated>2019-11-26T00:00:00-08:00</updated><id>https://ankushg.com/speaking/droidcon-sf-2019</id><content type="html" xml:base="https://ankushg.com/speaking/droidcon-sf-2019"><![CDATA[<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://player.vimeo.com/video/380848881?dnt=true" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>Learn how Quizlet uses Kotlin Multiplatform to build iOS, Android, and web learning experiences for over 50 million users per month.</p>

<p>By pinpointing the best areas of our codebase to share (and skipping over many others), Quizlet was able to use Kotlin Multiplatform to share the “secret sauce” of our product.</p>

<p>We did this without forcing our Android, iOS, and Web clients to follow a rigid, predetermined architecture. You’ll hear Quizlet’s journey getting Kotlin Multiplatform into production, how it compares to other approaches of sharing code, and (most importantly) lessons about shared code that we learned along the way.</p>]]></content><author><name>Ankush Gupta</name></author><category term="speaking" /><category term="android" /><category term="kotlin" /><category term="kotlin multiplatform" /><category term="kmp" /><category term="kmm" /><summary type="html"><![CDATA[droidcon SF 2019!]]></summary></entry><entry><title type="html">San Francisco Kotlin Meetup: Powering Worldwide Learning with Kotlin Multiplatform</title><link href="https://ankushg.com/speaking/sf-kotlin-meetup-2019" rel="alternate" type="text/html" title="San Francisco Kotlin Meetup: Powering Worldwide Learning with Kotlin Multiplatform" /><published>2019-11-07T00:00:00-08:00</published><updated>2019-11-07T00:00:00-08:00</updated><id>https://ankushg.com/speaking/sf-kotlin-meetup-2019</id><content type="html" xml:base="https://ankushg.com/speaking/sf-kotlin-meetup-2019"><![CDATA[<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/3NxnnyD0EZU" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>Learn how Quizlet uses Kotlin Multiplatform to build iOS, Android, and web learning experiences for over 50 million users per month.</p>

<p>By pinpointing the best areas of our codebase to share (and skipping over many others), Quizlet was able to use Kotlin Multiplatform to share the “secret sauce” of our product.</p>

<p>We did this without forcing our Android, iOS, and Web clients to follow a rigid, predetermined architecture. You’ll hear Quizlet’s journey getting Kotlin Multiplatform into production, how it compares to other approaches of sharing code, and (most importantly) lessons about shared code that we learned along the way.</p>]]></content><author><name>Ankush Gupta</name></author><category term="speaking" /><category term="android" /><category term="kotlin" /><category term="kotlin multiplatform" /><category term="kmp" /><category term="kmm" /><summary type="html"><![CDATA[San Francisco Kotlin Meetup. An official and free [Kotlin/Everywhere](https://events.withgoogle.com/kotlin-everywhere/) community event!]]></summary></entry><entry><title type="html">Using `@Parcelize` in Kotlin Multiplatform</title><link href="https://ankushg.com/posts/multiplatform-parcelize/" rel="alternate" type="text/html" title="Using `@Parcelize` in Kotlin Multiplatform" /><published>2019-10-20T00:00:00-07:00</published><updated>2019-10-20T00:00:00-07:00</updated><id>https://ankushg.com/posts/multiplatform-parcelize</id><content type="html" xml:base="https://ankushg.com/posts/multiplatform-parcelize/"><![CDATA[<p class="notice--warning">Jetbrains has deprecated the Kotlin Android Extensions plugin and separated <code class="language-plaintext highlighter-rouge">@Parcelize</code> into a new plugin which is maintained by Google. <a href="https://proandroiddev.com/migrating-the-deprecated-kotlin-android-extensions-compiler-plugin-to-viewbinding-d234c691dec7#e1a5">Read this article to see how to migrate to the new plugin</a>: it’s quite easy!</p>

<p class="notice--info">Corresponding code for this blog post <a href="https://github.com/ankushg/MultiplatformParcelize">is available on GitHub.</a></p>

<h1 id="background-on-parcels">Background on <code class="language-plaintext highlighter-rouge">Parcel</code>s</h1>

<p>The Android OS uses <a href="https://developer.android.com/reference/android/os/Parcel"><code class="language-plaintext highlighter-rouge">Parcel</code>s </a>as a high-performance method to communicate data. The Android-only <code class="language-plaintext highlighter-rouge">Parcelable</code> interface is used to denote classes that can easily be serialized to/from a <code class="language-plaintext highlighter-rouge">Parcel</code>.</p>

<p>They’re most frequently used with <code class="language-plaintext highlighter-rouge">Bundle</code> objects to communicate across activities and intents, and to store state across configuration changes.</p>

<p>As a result, most Android developers should be familiar with the concept of <code class="language-plaintext highlighter-rouge">Parcelable</code> classes.</p>

<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/parcels-800-670783ecd.jpg" srcset="/generated/assets/images/parcels-450-c740ac765.webp 450w, /generated/assets/images/parcels-650-c740ac765.webp 650w, /generated/assets/images/parcels-850-c740ac765.webp 850w, /generated/assets/images/parcels-1000-c740ac765.webp 1000w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="1600" height="1068" />
<figcaption>
      Photo by <a href="https://unsplash.com/photos/B6yDtYs2IgY">Joanna Kosinska</a>

    </figcaption></figure>

<h1 id="using-the-parcelize-android-extensions-plugin">Using the <code class="language-plaintext highlighter-rouge">@Parcelize</code> Android Extensions Plugin</h1>
<p>Hand-writing <code class="language-plaintext highlighter-rouge">Parcelable</code> implementations can be quite a pain. Thankfully, several tools exist to automagically generate the requisite code for you.</p>

<p>One such tool is the <code class="language-plaintext highlighter-rouge">@Parcelize</code> Kotlin compiler plugin. <code class="language-plaintext highlighter-rouge">@Parcelize</code> is included with Kotlin and can automatically <a href="https://kotlinlang.org/docs/tutorials/android-plugin.html#parcelable-implementations-generator">generate Parcelable implementations</a> for you! The following section is adapted from their documentation.</p>

<p>In the base case, the process is pleasantly simple:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">android.os.Parcelable</span>
<span class="k">import</span> <span class="nn">kotlinx.android.parcel.Parcelize</span>

<span class="nd">@Parcelize</span>
<span class="kd">data class</span> <span class="nc">User</span><span class="p">(</span>
  <span class="kd">val</span> <span class="py">userId</span><span class="p">:</span> <span class="n">long</span><span class="p">,</span>
  <span class="kd">val</span> <span class="py">userName</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">):</span> <span class="nc">Parcelable</span>
</code></pre></div></div>

<h2 id="advanced-parceling-logic">Advanced <code class="language-plaintext highlighter-rouge">Parcel</code>ing Logic</h2>

<p>The <code class="language-plaintext highlighter-rouge">@Parcelize</code> Android Extensions plugin has built-in support for several types, but also allows you to specify a custom parceling logic!</p>

<p>If your class requires advanced serialization logic, you can write it inside a companion class:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Parcelize</span>
<span class="kd">data class</span> <span class="nc">User</span><span class="p">(</span>
  <span class="kd">val</span> <span class="py">userId</span><span class="p">:</span> <span class="n">long</span><span class="p">,</span>
  <span class="kd">val</span> <span class="py">userName</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">):</span> <span class="nc">Parcelable</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">companion</span> <span class="k">object</span> <span class="p">:</span> <span class="nc">Parceler</span><span class="p">&lt;</span><span class="nc">User</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nc">User</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">parcel</span><span class="p">:</span> <span class="nc">Parcel</span><span class="p">,</span> <span class="n">flags</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
      <span class="nc">TODO</span><span class="p">(</span><span class="s">"my custom write implementation"</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">create</span><span class="p">(</span><span class="n">parcel</span><span class="p">:</span> <span class="nc">Parcel</span><span class="p">):</span> <span class="nc">User</span> <span class="p">{</span>
      <span class="nc">TODO</span><span class="p">(</span><span class="s">"my custom read implementation"</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If you are working with an unsupported type that you can’t personally modify the code for, you can supply an external <code class="language-plaintext highlighter-rouge">Parceler</code> implementation for it.</p>

<p>For example, if we used the <code class="language-plaintext highlighter-rouge">java.util.UUID</code> class instead of a <code class="language-plaintext highlighter-rouge">Long</code> to create a unique identifier for our <code class="language-plaintext highlighter-rouge">User</code>, we could write an external <code class="language-plaintext highlighter-rouge">Parceler</code> for <code class="language-plaintext highlighter-rouge">UUID</code> as follows:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">java.util.UUID</span>

<span class="k">import</span> <span class="nn">kotlinx.android.parcel.Parceler</span>

<span class="kd">object</span> <span class="nc">UUIDParceler</span> <span class="p">:</span> <span class="nc">Parceler</span><span class="p">&lt;</span><span class="nc">UUID</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="k">override</span> <span class="k">fun</span> <span class="nf">create</span><span class="p">(</span><span class="n">parcel</span><span class="p">:</span> <span class="nc">Parcel</span><span class="p">):</span> <span class="nc">UUID</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">mostSigBits</span> <span class="p">=</span> <span class="n">parcel</span><span class="p">.</span><span class="nf">readLong</span><span class="p">()</span>
    <span class="kd">val</span> <span class="py">leastSigBits</span> <span class="p">=</span> <span class="n">parcel</span><span class="p">.</span><span class="nf">readLong</span><span class="p">()</span>
    <span class="k">return</span> <span class="nc">UUID</span><span class="p">(</span><span class="n">mostSigBigs</span><span class="p">,</span> <span class="n">leastSigBits</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="k">override</span> <span class="k">fun</span> <span class="nc">UUID</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">parcel</span><span class="p">:</span> <span class="nc">Parcel</span><span class="p">,</span> <span class="n">flags</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">parcel</span><span class="p">.</span><span class="nf">writeLong</span><span class="p">(</span><span class="n">mostSigBits</span><span class="p">)</span>
    <span class="n">parcel</span><span class="p">.</span><span class="nf">writeLong</span><span class="p">(</span><span class="n">leastSigBits</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We can then apply our external <code class="language-plaintext highlighter-rouge">Parceler</code> using either the <code class="language-plaintext highlighter-rouge">@TypeParceler</code> or <code class="language-plaintext highlighter-rouge">@WriteWith</code> annotation:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">java.util.UUID</span>

<span class="k">import</span> <span class="nn">kotlinx.android.parcel.*</span>

<span class="c1">// Class-local parceler</span>
<span class="nd">@Parcelize</span>
<span class="nd">@TypeParceler</span><span class="p">&lt;</span><span class="nc">UUID</span><span class="p">,</span> <span class="nc">UUIDParceler</span><span class="p">&gt;()</span>
<span class="kd">data class</span> <span class="nc">User</span><span class="p">(</span>
  <span class="kd">val</span> <span class="py">id</span><span class="p">:</span> <span class="nc">UUID</span>
<span class="p">)</span>

<span class="c1">// Property-local parceler</span>
<span class="nd">@Parcelize</span>
<span class="kd">class</span> <span class="nc">User</span><span class="p">(</span>
  <span class="nd">@TypeParceler</span><span class="p">&lt;</span><span class="nc">UUID</span><span class="p">,</span> <span class="nc">UUIDParceler</span><span class="p">&gt;()</span>
  <span class="kd">val</span> <span class="py">id</span><span class="p">:</span> <span class="nc">UUID</span>
<span class="p">)</span>

<span class="c1">// Type-local parceler</span>
<span class="nd">@Parcelize</span>
<span class="kd">class</span> <span class="nc">User</span><span class="p">(</span>
  <span class="kd">val</span> <span class="py">id</span><span class="p">:</span> <span class="nd">@WriteWith</span><span class="p">&lt;</span><span class="nc">UUIDParceler</span><span class="p">&gt;()</span> <span class="nc">UUID</span>
<span class="p">)</span>
</code></pre></div></div>

<p>If you take a peek at the <a href="https://github.com/JetBrains/kotlin/tree/master/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel">source code for the Parcelize Android Extension</a>, you will find a few other annotations such as <code class="language-plaintext highlighter-rouge">@IgnoredOnParcel</code> and <code class="language-plaintext highlighter-rouge">@RawValue</code>, however their usage isn’t officially documented so I won’t get into them here 😉</p>

<h1 id="enter-kotlin-multiplatform">Enter Kotlin Multiplatform</h1>

<p>Generating <code class="language-plaintext highlighter-rouge">Parcelable</code> implementations using <code class="language-plaintext highlighter-rouge">@Parcelize</code> works great when you’re operating in the Android world. However, if you are working in a Kotlin Multiplatform Project, you will find that things aren’t quite so simple!</p>

<p>If you try to paste those earlier code samples into the common sourceset in a <a href="https://kotlinlang.org/docs/reference/multiplatform.html">Kotlin Multiplatform Project</a>, you’ll quickly find that you can’t import <code class="language-plaintext highlighter-rouge">Parcelable</code> or <code class="language-plaintext highlighter-rouge">@Parcelize</code>, <code class="language-plaintext highlighter-rouge">@TypeParceler</code> or <code class="language-plaintext highlighter-rouge">@WriteWith</code>!</p>

<p>If you take a step back however, this makes sense. The concept of <code class="language-plaintext highlighter-rouge">Parcelable</code> objects is Android-specific. <code class="language-plaintext highlighter-rouge">Parcel</code>-related classes simply don’t exist in non-Android environments.</p>

<p>But what do we do if we use a Kotlin Multiplatform library, and want to store some of those classes in an Android <code class="language-plaintext highlighter-rouge">Bundle</code>?</p>

<p>We <em>could</em> manyally declare external <code class="language-plaintext highlighter-rouge">Parceler</code>s for every class. We’d write them by hand, and then sprinkle  <code class="language-plaintext highlighter-rouge">@TypeParceler</code> or <code class="language-plaintext highlighter-rouge">@WriteWith</code> all over our Android codebase.</p>

<p>Or, we can take advantage of Kotlin Multiplatform’s<a href="https://kotlinlang.org/docs/reference/platform-specific-declarations.html"> powerful platform-specific declarations</a> to make things work for us!</p>

<h2 id="declaring-expect-definitions">Declaring <code class="language-plaintext highlighter-rouge">expect</code> Definitions</h2>

<p>In order to use these annotations and the <code class="language-plaintext highlighter-rouge">Parcelable</code> interface in common code, we must declare common-code versions of them.</p>

<p>First, let’s start with the <code class="language-plaintext highlighter-rouge">Parcelable</code> interface:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">com.example.parcel</span>

<span class="c1">// Common Code</span>
<span class="n">expect</span> <span class="kd">interface</span> <span class="nc">Parcelable</span>
</code></pre></div></div>

<p>By declaring an empty expected interface in our common sourceset, we will be able to write classes that implement <code class="language-plaintext highlighter-rouge">com.example.parcel.Parcelable</code>.</p>

<p>Let’s continue by defining the expected <code class="language-plaintext highlighter-rouge">@Parcelize</code> annotation:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">com.example.parcel</span>

<span class="c1">// Common Code</span>
<span class="nd">@UseExperimental</span><span class="p">(</span><span class="nc">ExperimentalMultiplatform</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="nd">@OptionalExpectation</span>
<span class="nd">@Target</span><span class="p">(</span><span class="nc">AnnotationTarget</span><span class="p">.</span><span class="nc">CLASS</span><span class="p">)</span>
<span class="nd">@Retention</span><span class="p">(</span><span class="nc">AnnotationRetention</span><span class="p">.</span><span class="nc">BINARY</span><span class="p">)</span>
<span class="n">expect</span> <span class="k">annotation</span> <span class="kd">class</span> <span class="nc">Parcelize</span><span class="p">()</span>
</code></pre></div></div>

<p>If you’re extra-observant, you might have noticed something different here. We’re using the <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-optional-expectation/index.html"><code class="language-plaintext highlighter-rouge">@OptionalExpectation</code></a> annotation, after opting-in to the experimental multiplatform feature.</p>

<p><code class="language-plaintext highlighter-rouge">@OptionalExpectation</code> can be added to <code class="language-plaintext highlighter-rouge">annotation class</code>es to denote that the class isn’t <em>required</em> to have an <code class="language-plaintext highlighter-rouge">actual</code> counterpart on every platform. If we use the annotation and compile for a platform where we don’t implement the annotation at all, the Kotlin compiler just pretends the annotation never existed!</p>

<blockquote>
  <p>If an optional annotation has no corresponding actual class on a platform, the annotation entries where it’s used are simply erased when compiling code on that platform.</p>
</blockquote>

<p>This is the same mechanism that Kotlin Multiplatform uses to allow us to use built-in annotations like <code class="language-plaintext highlighter-rouge">@JvmName</code> and <code class="language-plaintext highlighter-rouge">@JsName</code> in Common code.</p>

<p>Now that we’ve declared our <code class="language-plaintext highlighter-rouge">expect</code>ed classes, we have to <code class="language-plaintext highlighter-rouge">actual</code>ly define them on our platforms.</p>

<h2 id="android-land">Android-Land</h2>

<h3 id="building-a-real-android-library">Building a Real Android Library</h3>

<p class="notice--warning">Most default Kotlin Multiplatform templates write code for Android using the <code class="language-plaintext highlighter-rouge">jvm</code> target. In many cases, this is fine. However, in order to use the <code class="language-plaintext highlighter-rouge">@Parcelize</code> plugin, we will need to create an actual Android Library using the <code class="language-plaintext highlighter-rouge">com.android.library</code> Gradle plugin.</p>

<p><a href="https://github.com/ankushg/MultiplatformParcelize/commit/b05d5b8249dd9761f0d55da9fe1dfb787b689cd4">Take a peek at this commit on Github to see the full details</a>, but below are some of the changes you may need to make:</p>

<p>Adding the <code class="language-plaintext highlighter-rouge">com.android.library</code> and <code class="language-plaintext highlighter-rouge">kotlin-android-extensions</code> plugins:</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plugins</span> <span class="o">{</span>
  <span class="n">id</span> <span class="s1">'com.android.library'</span>
  <span class="n">id</span> <span class="s1">'org.jetbrains.kotlin.multiplatform'</span>
  <span class="n">id</span> <span class="s1">'kotlin-android-extensions'</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Adding an explicit <code class="language-plaintext highlighter-rouge">android()</code> target platform:</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kotlin</span> <span class="o">{</span>
  <span class="n">jvm</span><span class="o">()</span>
  <span class="n">android</span><span class="o">()</span>
  <span class="n">js</span> <span class="o">{</span>
    <span class="n">nodejs</span> <span class="o">{</span>
    <span class="o">}</span>
  <span class="o">}</span>

  <span class="c1">// Configuration...</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Adding an <code class="language-plaintext highlighter-rouge">android { }</code> block where we point to our sourceSets to the code for the <code class="language-plaintext highlighter-rouge">android()</code> target platform and enable the <code class="language-plaintext highlighter-rouge">@Parcelize</code> plugin:</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">android</span> <span class="o">{</span>
  <span class="n">compileSdkVersion</span> <span class="mi">28</span>

  <span class="n">androidExtensions</span> <span class="o">{</span>
    <span class="n">experimental</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="o">}</span>

  <span class="n">defaultConfig</span> <span class="o">{</span>
    <span class="n">minSdkVersion</span> <span class="mi">21</span>
    <span class="n">testInstrumentationRunner</span> <span class="s2">"androidx.test.runner.AndroidJUnitRunner"</span>
  <span class="o">}</span>

  <span class="c1">// Android Gradle Plugin expects sources to be in main, test, and androidTest</span>
  <span class="c1">// In order to keep our code structure consistent across platforms, we redefine</span>
  <span class="c1">// the sourceset directories here.</span>
  <span class="k">sourceSets</span> <span class="o">{</span>
    <span class="c1">// Main code is in androidMain</span>
    <span class="n">main</span> <span class="o">{</span>
      <span class="n">manifest</span><span class="o">.</span><span class="na">srcFile</span> <span class="s1">'src/androidMain/AndroidManifest.xml'</span>
      <span class="n">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'src/androidMain/kotlin'</span><span class="o">]</span>
      <span class="n">res</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'src/androidMain/res'</span><span class="o">]</span>
    <span class="o">}</span>

    <span class="c1">// Unit test code is in androidTest (to parallel jvmTest, jsTest)</span>
    <span class="n">test</span> <span class="o">{</span>
      <span class="n">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'src/androidTest/kotlin'</span><span class="o">]</span>
      <span class="n">res</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'src/androidTest/res'</span><span class="o">]</span>
    <span class="o">}</span>

    <span class="c1">// Android instrumentation test code is in androidInstrumentationTests</span>
    <span class="n">androidTest</span> <span class="o">{</span>
      <span class="n">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'src/androidInstrumentationTest/kotlin'</span><span class="o">]</span>
      <span class="n">res</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'src/androidInstrumentationTest/res'</span><span class="o">]</span>
    <span class="o">}</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="the-magic-of-actual-typealiases">The Magic of <code class="language-plaintext highlighter-rouge">actual typealias</code>es</h3>

<p>A powerful feature of Kotlin Multiplatform is that platform-specific declarations can be simple <code class="language-plaintext highlighter-rouge">typealias</code>es to existing classes.</p>

<p>This is great, because it allows us to point our <code class="language-plaintext highlighter-rouge">expect</code>ed interface and annotation to very real classes that we already have on Android!</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">com.example.parcel</span>

<span class="c1">// Android Code</span>
<span class="n">actual</span> <span class="k">typealias</span> <span class="nc">Parcelable</span> <span class="p">=</span> <span class="n">android</span><span class="p">.</span><span class="n">os</span><span class="p">.</span><span class="nc">Parcelable</span>
<span class="n">actual</span> <span class="k">typealias</span> <span class="nc">Parcelize</span> <span class="p">=</span> <span class="n">kotlinx</span><span class="p">.</span><span class="n">android</span><span class="p">.</span><span class="n">parcel</span><span class="p">.</span><span class="nc">Parcelize</span>
</code></pre></div></div>

<h2 id="every-other-platform">Every Other Platform</h2>

<p>Thanks to the magic of the <code class="language-plaintext highlighter-rouge">@OptionalExpectation</code> annotation, we don’t need to declare <code class="language-plaintext highlighter-rouge">actual</code> implementations for <code class="language-plaintext highlighter-rouge">@Parcelize</code> on non-Android platforms.</p>

<p>Unfortunately,  <code class="language-plaintext highlighter-rouge">@OptionalExpectation</code> only supports <code class="language-plaintext highlighter-rouge">annotation class</code>es. As a result, we <em>do</em> have to write an empty <code class="language-plaintext highlighter-rouge">actual interface Parcelable</code> for every platform:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">com.example.parcel</span>

<span class="c1">// Non-Android Code</span>
<span class="n">actual</span> <span class="kd">interface</span> <span class="nc">Parcelable</span>
</code></pre></div></div>

<p>Once we do this, we should be ready to roll!</p>

<h2 id="putting-it-all-together">Putting It All Together</h2>

<p>We can now use our custom <code class="language-plaintext highlighter-rouge">Parcelable</code> and <code class="language-plaintext highlighter-rouge">@Parcelize</code> definitions in Common Code!</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">com.example.parcel.*</span>

<span class="c1">// Common Code</span>
<span class="nd">@Parcelize</span>
<span class="kd">data class</span> <span class="nc">User</span><span class="p">(</span>
  <span class="kd">val</span> <span class="py">userId</span><span class="p">:</span> <span class="n">long</span><span class="p">,</span>
  <span class="kd">val</span> <span class="py">userName</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">):</span> <span class="nc">Parcelable</span>
</code></pre></div></div>

<p>In Android, we can take advantage of the class we defined in common code, and store it in a <code class="language-plaintext highlighter-rouge">Bundle</code>!</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Android Code</span>
<span class="kd">val</span> <span class="py">user</span> <span class="p">=</span> <span class="nc">User</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Android User"</span><span class="p">)</span>

<span class="kd">val</span> <span class="py">bundle</span> <span class="p">=</span> <span class="nc">Bundle</span><span class="p">().</span><span class="nf">apply</span> <span class="p">{</span>
  <span class="nf">putParcelable</span><span class="p">(</span>
    <span class="nc">USER_BUNDLE_KEY</span><span class="p">,</span>
    <span class="n">user</span>
  <span class="p">)</span>
<span class="p">}</span>

<span class="kd">val</span> <span class="py">unparceled</span><span class="p">:</span> <span class="nc">User</span><span class="p">?</span> <span class="p">=</span> <span class="n">bundle</span><span class="p">.</span><span class="nf">getParcelable</span><span class="p">(</span><span class="nc">USER_BUNDLE_KEY</span><span class="p">)</span>

<span class="nf">assertEquals</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">unparceled</span><span class="p">)</span> <span class="c1">// Nice!</span>
</code></pre></div></div>

<p>You might notice that we didn’t go into the details needed for <code class="language-plaintext highlighter-rouge">@TypeParceler</code> or <code class="language-plaintext highlighter-rouge">@WriteWith</code>.</p>

<p>While these classes certainly <em>can</em> be added to a Kotlin Multiplatform project in this manner, doing so requires creating <code class="language-plaintext highlighter-rouge">expect</code>/<code class="language-plaintext highlighter-rouge">actual</code> declarations for many more classes (for example, <code class="language-plaintext highlighter-rouge">Parcel</code>, <code class="language-plaintext highlighter-rouge">kotlinx.android.parcel.Parceler</code>), and their methods.</p>

<p>For this reason, these extra steps (if you need them) are left as an exercise for the reader 😉</p>

<h1 id="wrapping-up">Wrapping Up</h1>

<p>The same principles used here can apply to many other platform-specific annotations, classes, and interfaces.</p>

<p>If you’ve got other cool ideas for how this technique could be used, <a href="https://twitter.com/ankushg">drop me a tweet</a>!</p>]]></content><author><name>Ankush Gupta</name></author><category term="posts" /><category term="kotlin" /><category term="android" /><category term="multiplatform" /><summary type="html"><![CDATA[Using Platform-Specific Declarations to unlock `@Parcelize` in [Kotlin Multiplatform](https://www.jetbrains.com/lp/mobilecrossplatform/) Projects]]></summary></entry><entry><title type="html">Principles of Shared Code</title><link href="https://ankushg.com/speaking/denver-startup-week-2019" rel="alternate" type="text/html" title="Principles of Shared Code" /><published>2019-09-17T00:00:00-07:00</published><updated>2019-09-17T00:00:00-07:00</updated><id>https://ankushg.com/speaking/denver-startup-week-2019</id><content type="html" xml:base="https://ankushg.com/speaking/denver-startup-week-2019"><![CDATA[<p>Writing effective, sustainable, and flexible multiplatform code has been the White Whale of many a software engineer’s career. At Quizlet, we’ve spent several years exploring and refining how to use shared code to develop a high-quality learning experience for over 50 million monthly active users.</p>

<p>In this talk, you’ll learn the guiding principles behind Quizlet’s approach to shared code, regardless of which technologies you use.</p>

<p>We’ll begin by exploring the business need for shared code at Quizlet. You’ll learn about popular approaches to dealing with shared code across the industry, and we’ll share how we identified which areas of our codebase that were good candidates for sharing cross-platform, and which were best left for platform-specific code.</p>

<p>We’ll detail our initial foray into shared code using Javascript, and explain both its benefits and drawbacks.</p>

<p>We’ll then switch gears and explore Kotlin Multiplatform – Quizlet’s current technology of choice for shared code. We’ll share all the spicy details:</p>

<ul>
  <li>How we evaluated Kotlin Multiplatform as a technology</li>
  <li>How writing and distributing Kotlin Multiplatform code works</li>
  <li>Our path to successfully migrating our shared codebase into this new paradigm</li>
  <li>The impacts of switching to Kotlin Multiplatform for both our developers and end-users (spoiler alert: better mobile developer experience, huge reductions to app size and crash rates on mobile platforms, and minimal overhead on web)</li>
</ul>]]></content><author><name>Ankush Gupta</name></author><category term="speaking" /><category term="android" /><category term="kotlin" /><category term="kotlin multiplatform" /><category term="kmp" /><category term="kmm" /><summary type="html"><![CDATA[Denver Startup Week 2019. Free community event!]]></summary></entry><entry><title type="html">A Journey in Shared Code with Kotlin Multiplatform</title><link href="https://ankushg.com/speaking/droidcon-berlin-2019" rel="alternate" type="text/html" title="A Journey in Shared Code with Kotlin Multiplatform" /><published>2019-07-03T00:00:00-07:00</published><updated>2019-07-03T00:00:00-07:00</updated><id>https://ankushg.com/speaking/droidcon-berlin-2019</id><content type="html" xml:base="https://ankushg.com/speaking/droidcon-berlin-2019"><![CDATA[<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://player.vimeo.com/video/374874028?dnt=true" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>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.</p>

<p>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.</p>

<p>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.</p>

<p>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!</p>]]></content><author><name>Ankush Gupta</name></author><category term="speaking" /><category term="android" /><category term="kotlin" /><category term="kotlin multiplatform" /><category term="kmp" /><category term="kmm" /><summary type="html"><![CDATA[droidcon Berlin 2019: My first time speaking at a conference!]]></summary></entry><entry><title type="html">Working with TLS 1.2 on Android 4.4 and Lower</title><link href="https://ankushg.com/posts/tls-1.2-on-android/" rel="alternate" type="text/html" title="Working with TLS 1.2 on Android 4.4 and Lower" /><published>2018-08-03T00:00:00-07:00</published><updated>2018-08-03T00:00:00-07:00</updated><id>https://ankushg.com/posts/tls-1.2-on-android</id><content type="html" xml:base="https://ankushg.com/posts/tls-1.2-on-android/"><![CDATA[<figure class="">
  
    
  

  <img alt="" src="/generated/assets/images/tls-1.2-bugdroid-800-5d3877716.png" srcset="/generated/assets/images/tls-1.2-bugdroid-450-ea832720f.webp 450w, /generated/assets/images/tls-1.2-bugdroid-650-ea832720f.webp 650w, /generated/assets/images/tls-1.2-bugdroid-850-ea832720f.webp 850w, /generated/assets/images/tls-1.2-bugdroid-956-ea832720f.webp 956w" sizes="(max-width: 768px) calc(100vw - 32px), (max-width: 1024px) calc(100vw - 36px), (max-width: 1280px) calc(100vw - 440px), 636px" width="956" height="1200" />
<figcaption>
      The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License

    </figcaption></figure>

<p><a href="https://blog.pcisecuritystandards.org/are-you-ready-for-30-june-2018-sayin-goodbye-to-ssl-early-tls">As described by the PCI Security Standards Council</a>, early versions of the TLS security protocol are susceptible to widespread exploits that could expose potentially sensitive data. On June 30th, <a href="https://quizlet.com/blog/quizlet-is-upgrading-our-https-security">we upgraded Quizlet’s security to require TLS 1.2 or higher</a> as part of our continued following of best-practice data security recommendations.</p>

<p>While our web client and iOS app were largely unaffected by this transition, we had to do a bit of work to make sure our Android app was up to the job.</p>

<p>This was due to a few reasons:</p>

<ol>
  <li>There is a lack of clarity surrounding TLS 1.2 support on older Android devices,</li>
  <li>Device manufacturers have differing commitments to the official Android specs for shipping TLS 1.2 on their devices</li>
  <li>Carriers and device manufacturers have differing commitments to providing software and security updates to their customers</li>
</ol>

<p>There’s unfortunately no simple way to ensure <em>all</em> of our users are seamlessly able to use TLS 1.2 to access Quizlet, but we’ve tried our best to minimize disruption to our users as we made the transition.</p>

<p>In this post, I want to share how we went about doing it.</p>

<h2 id="installing-tls-12-where-needed">Installing TLS 1.2 Where Needed</h2>

<p>We support Android 4.1–4.3 (API 16–18) with a long-term-support version of our app, and Android 4.4+ (API 19+) in our latest builds. Our goal was to minimize disruption for users in both of those builds, so our target was ensuring TLS 1.2 support for Android 4.1 and higher.</p>

<p>The first thing we realized was that <a href="https://developer.android.com/reference/javax/net/ssl/SSLSocket#default-configuration-for-different-android-versions">despite documentation suggesting otherwise</a>, not all devices on Android 4.1+ actually support TLS 1.2. Even though it is likely due to device manufacturers not fully following the official Android specs, we had to do what we could to ensure this would work for our users.</p>

<p>Luckily, Google Play Services <a href="https://developer.android.com/training/articles/security-gms-provider">provides a way to do this</a>. The solution is to use <a href="https://developers.google.com/android/reference/com/google/android/gms/security/ProviderInstaller"><code class="language-plaintext highlighter-rouge">ProviderInstaller</code></a> from Google Play Services to try to update the device to support the latest and greatest security protocols.</p>

<p>We do this as follows:</p>

<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/034c151ac6ed4ed5c89d3065e2899149.js?file=InstallTls12.kt"> </script>

<p>For devices without Google Play services, we unfortunately don’t have an alternative way to patch the device’s security <code class="language-plaintext highlighter-rouge">Provider</code> to support TLS 1.2. For these users, we display a message so they know the app may not work properly for them.</p>

<p>In the Quizlet Android app, we call this in a background thread if our first API call fails with an <code class="language-plaintext highlighter-rouge">SSLHandshakeException</code>. Calling <code class="language-plaintext highlighter-rouge">ProviderInstaller.installIfNeeded</code> only <em>after</em> an <code class="language-plaintext highlighter-rouge">SSLException</code> allows us to support users with TLS 1.2 already installed with minimal disruption (i.e., if they have an old version of Google Play Services, or don’t have it installed at all). In your apps, it might make sense to handle this logic in a similar manner.</p>

<h3 class="notice--info" id="calling-providerinstallerinstallifneeded-only-after-an-sslexception-allows-us-to-support-users-with-tls-12-already-installed-with-minimal-disruption">Calling <code class="language-plaintext highlighter-rouge">ProviderInstaller.installIfNeeded</code> only <em>after</em> an <code class="language-plaintext highlighter-rouge">SSLException</code> allows us to support users with TLS 1.2 already installed with minimal disruption</h3>

<p>Alternatives to this approach include Google’s official suggestions: either blocking network transactions by calling <code class="language-plaintext highlighter-rouge">installIfNeeded</code> upon creation of all network-specific threads (it’s a no-op if the security <code class="language-plaintext highlighter-rouge">Provider</code> is already up-to-date, so the performance impact should be minimal), or <a href="https://developer.android.com/training/articles/security-gms-provider#example_async">using the asynchronous variant</a> to start a call from the main thread.</p>

<p>Originally, we thought we were done after doing this step. However, after further testing, we noticed something peculiar. Many devices succeeded in updating the security <code class="language-plaintext highlighter-rouge">Provider</code> via <code class="language-plaintext highlighter-rouge">installIfNeeded</code>, but were still throwing <code class="language-plaintext highlighter-rouge">SSLHandshakeExceptions</code> when the server required TLS 1.2!</p>

<h2 id="forcing-tls-12-to-be-enabled">Forcing TLS 1.2 To Be Enabled</h2>

<p>Our initial understanding of <a href="https://github.com/square/okhttp/wiki/HTTPS">OkHttp’s HTTPS strategy</a> was that it uses the highest-installed version of TLS, and it falls back to older protocols if newer ones are not installed. This is false.</p>

<p>OkHttp’s default behavior is to use an <code class="language-plaintext highlighter-rouge">SSLSocketFactory</code> to create <code class="language-plaintext highlighter-rouge">SSLSocket</code>s with default parameters, and select from protocols that are <em>enabled</em> for a given <code class="language-plaintext highlighter-rouge">SSLSocket</code>. Here’s the kicker: The latest protocols <strong><em>installed</em></strong> on a device are not necessarily <strong><em>enabled</em></strong> on its default <code class="language-plaintext highlighter-rouge">SSLSocket</code>s!</p>

<h3 class="notice--warning" id="the-latest-protocols-installed-on-a-device-are-not-necessarily-enabled-on-its-default-sslsockets">The latest protocols <strong><em>installed</em></strong> on a device are not necessarily <strong><em>enabled</em></strong> on its default <code class="language-plaintext highlighter-rouge">SSLSocket</code>s!</h3>

<p>The <a href="https://developer.android.com/reference/javax/net/ssl/SSLSocket#default-configuration-for-different-android-versions">docs for <code class="language-plaintext highlighter-rouge">SSLSocket</code> on Android</a> state that TLS 1.2 is only enabled as a default client protocol starting in Android 4.3. To make things more confusing, developers have found that certain Samsung devices on Android 4.4 don’t have it enabled either.</p>

<p>To fix this, we need to override OkHttp’s <code class="language-plaintext highlighter-rouge">SSLSocketFactory</code> to enable TLS 1.2 on all <code class="language-plaintext highlighter-rouge">SSLSocket</code>s. We adapted the helper class from <a href="https://github.com/square/okhttp/issues/2372#issuecomment-244807676">this Github issue</a>, and <a href="https://gist.github.com/ankushg/8c0c3144318b1c17abb228d6211ba996">cleaned it up a bit to make it more Kotlin-tastic</a>. We cleared up deprecation warnings by explicitly passing in an <code class="language-plaintext highlighter-rouge">X509TrustManager</code> and made use of Kotlin’s extension functions to simplify some of the code, but the actual logic is largely the same.</p>

<p>The new-and-improved <code class="language-plaintext highlighter-rouge">Tls12SocketFactory</code> is a bit too long to embed, but <a href="https://gist.github.com/ankushg/8c0c3144318b1c17abb228d6211ba996">here’s a link</a>.</p>

<p>Now, we can simply call the <a href="https://gist.github.com/ankushg/8c0c3144318b1c17abb228d6211ba996#file-tls12socketfactory-kt-L38"><code class="language-plaintext highlighter-rouge">enableTls12()</code></a> extension function in our <code class="language-plaintext highlighter-rouge">OkHttpClient.Builder</code> chain in Dagger, and there we have it!</p>

<p>Or so we thought.</p>

<h2 id="forcing-tls-12-to-be-enabled-take-2-glide">Forcing TLS 1.2 To Be Enabled, Take 2: Glide</h2>

<p>It turns out this still wasn’t enough! Our regular-old API calls were working just fine, but all of our images were still failing to load through Glide due to even more <code class="language-plaintext highlighter-rouge">SSLHandshakeExceptions</code>.</p>

<p>By default, the Glide OkHttp3 integration uses a vanilla <code class="language-plaintext highlighter-rouge">OkHttpClient</code> to load images from URLs. This means it uses the regular <code class="language-plaintext highlighter-rouge">SSLSocketFactory</code> that does not forcefully enable TLS 1.2.</p>

<p>Lucky for us, this fix wasn’t too complicated for Glide. We <a href="https://bumptech.github.io/glide/doc/configuration.html#appglidemodule">set up our <code class="language-plaintext highlighter-rouge">AppGlideModule</code></a> to use our <code class="language-plaintext highlighter-rouge">Tls12SocketFactory</code> for its <code class="language-plaintext highlighter-rouge">OkHttpClient</code> as well:</p>

<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/88140bc973a1348b789064c926f0ad10.js?file=QuizletGlideModule.kt"> </script>

<p>If you use Glide or any other libraries that manage their own network calls, you should make sure that their <code class="language-plaintext highlighter-rouge">SSLSocket</code>s are also configured to enable TLS 1.2.</p>

<h3 class="notice--info" id="if-you-use-glide-or-any-other-libraries-that-manage-their-own-network-calls-you-should-make-sure-that-their-sslsockets-are-also-configured-to-enable-tls-12">If you use Glide or any other libraries that manage their own network calls, you should make sure that their <code class="language-plaintext highlighter-rouge">SSLSocket</code>s are also configured to enable TLS 1.2.</h3>

<p>Whew. <strong>Now</strong> we’re done!</p>

<h2 id="in-summary">In Summary</h2>

<p>A device can be in one of several states with regards to supporting TLS 1.2:</p>

<ol>
  <li>Device does not have TLS 1.2 installed at all.</li>
  <li>Device has TLS 1.2 installed, [<em>but not enabled by default.](https://developer.android.com/reference/javax/net/ssl/SSLSocket#default-configuration-for-different-android-versions)</em></li>
  <li>Device has TLS 1.2 installed and enabled by default.</li>
</ol>

<p>It’s important to make sure you handle not only installing TLS 1.2 support (via <code class="language-plaintext highlighter-rouge">ProviderInstaller</code>) but also ensuring that it’s enabled for all of your network connections — even ones that are handled automagically by other libraries!</p>]]></content><author><name>Ankush Gupta</name></author><category term="posts" /><category term="mobile" /><category term="android" /><category term="security" /><category term="cryptography" /><category term="kotlin" /><summary type="html"><![CDATA[Early versions of the TLS security protocol are susceptible to widespread exploits. In this article, I explain how we migrated the [Quizlet](https://quizlet.com) [Android app](https://play.google.com/store/apps/details?id=com.quizlet.quizletandroid) to TLS 1.2, while minimizing disruption to our users.]]></summary></entry><entry><title type="html">Cross-Engine Query Execution in Federated Database Systems</title><link href="https://ankushg.com/projects/thesis" rel="alternate" type="text/html" title="Cross-Engine Query Execution in Federated Database Systems" /><published>2016-05-01T00:00:00-07:00</published><updated>2016-05-01T00:00:00-07:00</updated><id>https://ankushg.com/projects/thesis</id><content type="html" xml:base="https://ankushg.com/projects/thesis"><![CDATA[<p>Duggan et al. created a reference implementation of the BigDAWG system: a new architecture for future Big Data applications, guided by the philosophy that “one size does not fit all”.</p>

<p>Such applications not only call for large-scale analytics, but also for real-time streaming support, smaller analytics at interactive speeds, data visualization, and cross-storage-system queries.</p>

<p>The importance and effectiveness of such a system has been demonstrated in a hospital application using data from an intensive care unit (ICU).</p>

<p>In my Master’s Thesis, I implemented and evaluated a cross-system Query Executor. I focused on cross-engine shuffle joins, taking into account the skew of the data distribution.</p>

<p><a href="/assets/docs/thesis.pdf">Download the paper</a></p>]]></content><author><name>Ankush Gupta</name></author><category term="projects" /><category term="database systems" /><category term="distributed systems" /><summary type="html"><![CDATA[👨🏻‍🎓 In my Master's Thesis, I implemented and evaluated a Query Executor that can evaluate queries joining across relational, column-store, document, and time-series databases.]]></summary></entry></feed>