Target’s mobile engineering teams strongly believe that native is the way to go for the applications that we offer to our guests. Native applications provide a top-tier user experience and allow us to leverage the platform expertise of our Android and iOS engineers.

However, there are mundane aspects of native app development that are common between both platforms and prone to creating minor implemention differences between platforms. When each platform makes an API call, for example, differences in how each treats missing fields or parses enumerated fields can lead to vastly different experiences.

We strive for feature parity between our Android and iOS applications, so our Gift Registry team decided to take a crack at minimizing these types of issues by creating a cross-platform library to handle interacting with our APIs in a consistent manner.

For the sake of keeping our initial foray into a cross-platform library simple, we decided to start by only parsing the API’s JSON response and returning the parsed objects to our native clients. This would prove out whether Kotlin Multiplatform was a viable path forward and would help uncover any issues we would have to deal with if we decided to expand the library’s scope.

The apps are introducing a brand new feed on our home screen, which presented us with a great test for this approach. Since this is a new feature, we didn’t need to replace any existing app code and we could design both our app code and our library in tandem.

Why Kotlin Multiplatform?

The Gift Registry Android app has been 100% Kotlin since we recreated the app from scratch in early 2017. The team has thoroughly enjoyed developing using Kotlin, and the incredible growth in Kotlin adoption has been very encouraging. Kotlin Multiplatform caught our attention because it compiles down to native code on both Android and iOS and because it enables us to keep using Kotlin (no JavaScript here!).

Getting the team’s iOS developers on board with Kotlin was easy- Kotlin and Swift have a lot in common, so using Kotlin didn’t feel like a step backward.

Having both platform teams on board was important to us. We typically develop new features in parallel, so we wanted all our engineers to be able to contribute to the library and fix any issues they uncovered during feature development. Plus there wouldn’t be much benefit in developing a library for two clients if one of the two dislikes the library!

Project Set-up

Our goal was to use Kotlin Multiplatform to generate separate libraries for Android and iOS. It would then be incorporated in each of the apps. This helped keep the scope limited if we had to deviate from our goal. Our library uses two Gradle plugins to accomplish our goals:

  • kotlin-multiplatform - compiles our Kotlin code into a JAR for Android and Frameworks for iOS
  • kotlinx-serialization - Kotlin’s first-party serialization library

Android

Getting the library integrated in Android was incredibly easy, as we expected. Our library project uses the JVM preset for our Android target. We grab the JAR output, add it to our application project like we would with any other JAR, and everything works!

iOS

iOS proved to be more challenging than expected to set up. Since Kotlin Multiplatform is very new, there isn’t a lot of documentation for it, and there are fewer example projects. Of the samples that exist, all are projects that use Kotlin Multiplatform as part of the app’s phase where the Kotlin is compiled and linked during the app’s build phase. We just wanted a stand-alone library that existing native clients can consume!

Most of the samples instruct you to modify your build.gradle to use the correct iOS target preset based on whether you are building for the iOS simulator or a real device. This simply doesn’t work when we want to export a static library. We ended up having Kotlin Multiplatform generate Frameworks for all three iOS architectures we use, and writing a Gradle task that uses Apple’s lipo tool to combine those three Frameworks into a single one that the iOS team could include.

Because of Apple’s restrictions on what architectures you can include when releasing to the App Store, the iOS team also has to strip out the simulator architecture when building for release. This isn’t ideal but works well enough for our workflow.

Kotlin Multiplatform generates an iOS framework with a header containing all Objective C types. Accessing them and using the methods was typical to most other iOS libraries. There were a few lines in the header file that caused issues for the compiler and we had to remove them as part of our gradle process. These were documented by other Kotlin Multiplatform users and were easy to locate and remedy.

Methods that could throw exceptions in Kotlin are not expressed in the framework header in a way for swift to catch and handle them. Thus any exception thrown to the iOS app would cause a crashing exception for the app. We overcame this by returning Kotlin pairs in our responses with an (Error?, Object?) pattern. This pattern is common in iOS and was easy to implement. Any exceptions were caught within the framework and returned as an error that the iOS app can handle appropriately.

What’s Next

So far we have been absolutely loving our Kotlin Multiplatform library. While the initial setup took a bit of time, our team has already been enjoying the benefits. The API we are parsing for the home screen feed was under heavy development as we created the library, and we were able to quickly iterate on the data models in our library then push those changes to the app codebases.

We would like to expand the scope of the library to not just parse the JSON response, but actually make the API calls as well. KTOR is looking like a promising path for accomplishing that goal. Keeping HTTP timeouts, request parameters, and more in sync will be much easier!

Analytics is another area that we could benefit from using a cross platform solution. As we worked on this library, it became clear that a second multiplatform project for analytics would make our lives significantly easier. Having a common logging events and attributes would ensure that we send the same content.

Bryan Herbst is a Lead Engineer working on Target’s Android apps. He regularly gives talks on emerging Android technology at local developer meetups. Nathan F Johnson is a senior engineer who specializes in creating amazing user experiences. Manij is a Senior Engineer Manager with the mobile apps teams. He is also a co-organizer of Twin Cities Kotlin User Group.