Exploring the Benefits of async/await with Kotlin Multiplatform Development

Chris and Nick discuss using Kotlin Mutliplatform for Mobile App Development

Insights and considerations of using Kotlin Multiplatform as a Software Engineer

Over a number of years, and on a number of projects, we've invested in using Kotlin Multiplatform (KMP) to provide a shared data layer for our native iOS and Android projects. This gives a good, consistent basis to build the app UIs from, and provides a time reduction from not having to build and maintain 2 API clients.

Since our first KMP project, both Swift and KMP itself have evolved significantly. As an example, when we first started using KMP, we had to manually define a wrapper for each Kotlin suspend function that allowed the iOS client to pass in a callback closure. From Kotlin 1.4.0 (https://kotlinlang.org/docs/whatsnew14.html#support-for-kotlin-s-suspending-functions-in-swift-and-objective-c), this was provided automatically allowing us to remove all our boilerplate code. The next step was the introduction of async/await in Swift 5.5.

Around Christmas 2022 I spent some R&D time looking at the new Swift async/await functionality, particularly in the context of a Kotlin Multiplatform project. I wanted to make use of the async functions that are provided by Swift’s Obj-C interop and move away from the callback functions that we'd been using. At the time I encountered an issue with calling Kotlin suspend functions for an async context (i.e. not the main thread) and discovered that we would need to upgrade to Kotlin 1.7.20 to be able to take my investigation further (https://youtrack.jetbrains.com/issue/KT-51297/Native-allow-calling-Kotlin-suspend-functions-on-non-main-thread-from-Swift).

While I progressed the R&D investigation further with a proof-of-concept upgrade of the project, other priorities meant that we weren't in a position to actually upgrade the project at that point. Now over a year later, with the Kotlin upgrade already done, it seemed like a good time to revisit the use of async/await functions in the context of a Kotlin Multiplatform project.

Click here to read more about Brightec’s explorations of using Kotlin Mutliplatform.

What I’ve learnt while Refactoring a Kotlin Multiplatform Project

While trying to refactor an existing project, there are a few things I learnt that are worth sharing.

  • You’ll need to pick a starting point. Find a function that makes a KMP call and refactor it to an async method. This will break any functions that call this refactored function so refactor those and keep going. It can be a bit of a never-ending task if you pick the wrong starting point so be prepared to back out and try a different starting point if the refactor is becoming too big. Click here to read more about async.

  • OperationQueues might be a good candidate to start with. We had one instance where we’d used a queue to chain multiple KMP calls together. Instead of adding dependencies between operations, it was much cleaner to just await the responses.

  • Another candidate is helpers/managers. We had a WishListManager object that handled adding and removing items from wish lists across the app. It originally used a callback syntax which was perfect for refactoring into async methods. Having refactored the WishListManager itself to provide an async API which made use of the async KMP calls, I was then able to update the rest of the code base to call these methods. For me this was achievable in a single PR however, for larger refactors, you could maintain both the callback and async functions in parallel and slowly move over to the async calls.

  • Consider your reviewer. A large/complicated diff will make it hard for your reviewer to understand what you’ve changed and whether anything may have broken. You can help to improve this by committing at appropriate points but, as with all branches, keep an eye on the overall size of the change that you’re making.

  • Consider when to use Tasks. While refactoring, you’ll probably find yourself constantly thinking about how to call an async function - do you make the calling function async as well, pushing the problem to the next caller, or do you wrap the call in a Task? At some point, you’ll have to create a Task but my general approach was to prefer making a method async over creating a Task. Hopefully, this will lead to cleaner code but only time will tell.

  • Finally, a bit of housekeeping. To get this to all work, there were a couple of changes to make on the KMP side:

    • Add kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none to the gradle.properties file

    • Remove any @ThreadLocal annotations, otherwise you’ll get different instances of those objects from each thread

Hopefully these points will help you to make use of the latest syntax and features when using Kotlin Multiplatform from an iOS project.

Click here to read more about iOS projects at Brightec.

Looking for something else?

Search over 400 blog posts from our team

Want to hear more?

Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!