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

Kotlin/Native at Careem: Experience Report

Kotlin/Native at Careem: Experience Report

At Careem we've been using Kotlin/Native to share business logic between iOS and Android apps, it's time to share our experience.

Nikolay Kasyanov

November 20, 2019
Tweet

Other Decks in Programming

Transcript

  1. What is Careem? • Ride-hailing • Operating in MENA region

    • First unicorn of the region • Food & package delivery • Payments 2
  2. How it all Started • Android-only app • Codebase was

    massive and monolithic • Hard to add features and fix bugs • Heavy reliance on manual testing 4
  3. 6 • Business need for iOS app for captains •

    Translating Kotlin to Swift didn’t go well • Kotlin/Native started to become stable enough at the time • Management was excited and skeptical at the same time How it all Started
  4. Kotlin • Kotlin is a cross-platform, statically typed, general-purpose programming

    language with type inference (Wikipedia) • JVM language originally • Open source including tooling • https://github.com/jetbrains/kotlin • Lots of similarities with Swift 7
  5. Kotlin/Native • LLVM-based Kotlin compiler backend & runtime • Reference

    counting based garbage collection • Open source: • https://github.com/jetbrains/kotlin-native • Imposes no UI paradigm nor architecture 8
  6. • Business logic is written in Kotlin and shared •

    Android app uses Kotlin code as-is • iOS app uses a prebuilt framework • It just works!* 9 How it Works
  7. Integration • From Xcode’s point of view it’s just a

    framework with Objective-C header • Add it to your project however you want • There’s a way to integrate Gradle build in your Xcode build 10
  8. data class Pair<out A, out B>(val first: A, val second:

    B) __attribute __((objc_subclassing_restricted)) __attribute __((swift_name("KotlinPair"))) @interface CMEKotlinPair< __covariant A, __covariant B> : KotlinBase - (instancetype)initWithFirst:(A _Nullable)first second:(B _Nullable)second __attribute __((swift_name("init(first:second:)"))) __attribute __((objc_designated_initializer)); - (A _Nullable)component1 __attribute __((swift_name("component1()"))); - (B _Nullable)component2 __attribute __((swift_name(“component2()"))); - (CMEKotlinPair<A, B> *)doCopyFirst:(A _Nullable)first second:(B _Nullable)second __attribute __((swift_name("doCopy(first:second:)"))); - (BOOL)equalsOther:(id _Nullable)other __attribute __((swift_name("equals(other:)"))); - (int32_t)hashCode __attribute __((swift_name("hashCode()"))); - (NSString *)toString __attribute __((swift_name("toString()"))); @property (readonly) A _Nullable first __attribute __((swift_name("first"))); @property (readonly) B _Nullable second __attribute __((swift_name("second"))); @end; 11 Objective-C
  9. Language Interop • Most types are bridged to Objective-C types

    • Objective-C as the “lowest common denominator” • limited generics • no default parameters • Some Kotlin is lost in translation • Works for us, YMMV 12 interface BookingStateStoreListener : StoreListener<BookingStoreState>
  10. Day to Day Development • Change Kotlin code • Ensure

    it’s working by unit and integration tests • Build & publish klibs to local Maven repository • Rebuild iOS framework, test the app 13
  11. How it Works – Building • A Gradle module in

    project working tree • Binary is rebuilt on demand & committed 14
  12. • expect/actual • it’s all Kotlin ➡ Platform Access 15

    expect class Date() { val current: Long } // iOS source set import platform.Foundation.NSDate actual class Date actual constructor () { actual val current: Long get() = NSDate().timeIntervalSince1970().toMillis() } // Android source set actual class Date actual constructor() { actual val current: Long = System.currentTimeMillis() }
  13. Concurrency • Kotlin Native memory model and concurrency are very

    special • Object can’t be shared between threads without being frozen • Coroutine support is very limited 16
  14. • actual/expect • Dependency Injection • define an interface in

    Kotlin • implement the protocol in Swift/Objective-C Platform Access 17
  15. Platform Access 18 interface SyncBookingsApi { fun syncBookings(completionHandler: (SyncBookingsModel?, Error?)

    -> Unit) } class BookingApi { let urlSession: URLSession func syncBookings(completionHandler: @escaping (CME.SyncBookingsModel?, KotlinError?) -> Void) { let endpoint = SyncBookingsEndpoint() urlSession.callEndpoint(endpoint) { result in switch result { case .success(let model): completionHandler(model, nil) case .failure(error): completionHandler(nil, error.toKotlinError()) } } } }
  16. Concurrency - Options • Run Kotlin & access Kotlin objects

    in one thread • delegate async processing to host platform • optionally freeze what you can & pass around • Wait a little more, proper coroutines are coming soon 19
  17. Takeaways • Kotlin/Native is not experimental technology anymore • Consider

    language interop & multithreading implications on API design & implementation • Accessing platform services isn’t an issue • Be conscious what to share 20
  18. • Getting iOS folks on board to use and contribute

    to shared codebase • Avoiding silos • Sharing ownership • Evolving shared code 21 Challenges
  19. What Helped • RFC process for big changes • Discussing

    smaller changes in advance • At least one pull request approval per platform • Maintaining changelog 22
  20. Conclusion Kotlin/Native helped us: • Support millions of rides monthly

    • Deal with complex business domain by only having to translate it into the code once • Have a better shared understanding of the business • Deliver iOS app faster It may work for you as well! 23