Industry: Medical Systems, Personal Health, Consumer Electronics
JetBrains products used: Kotlin Multiplatform
Philips ↗ is a leading health technology company focused on improving people's health and well-being, and enabling better outcomes across the health continuum – from healthy living and prevention, to diagnosis, treatment and home care.
Philips is pursuing the goal of improving 2.5 billion lives per year by 2030. Philips has a wide range of connected products for smart home care and personal health that are supported with smart applications for Android and iOS. Typically these products are connected locally via Bluetooth Low Energy or Wi-Fi, and they are also connected to the Philips cloud for remote control or data insights. For these purposes, the Philips Innovation Services organizational unit develops a connectivity platform that comprises embedded hardware, firmware, protocols, and an SDK for creating mobile apps. This connectivity SDK offers components for discovering, connecting to, and interacting with connected products. The SDK targets Android and iOS, with embedded Linux as the native target also becoming more important.
Installed base of mobile apps for Philips' connected products
The mobile apps for Philips' connected products have a combined installation count of well over 1 million on the App Store and Google Play. These apps are being installed and used all around the world, with the USA, Europe, and Asia being the biggest markets.
How is Kotlin Multiplatform Mobile used in your product?
One of our SDK components is a client library for Philips' cloud solution: HealthSuite Digital Platform, or HSDP for short. It enables an app developer
to interact easily with the cloud infrastructure without having to write HTTP
requests with intricate headers and complex payloads. Instead, developers can
call high-level functions, such as
getBlob() to download a binary file,
to control a cloud-connected device remotely, or
createDataItem() to upload telemetry
Boilerplate code generation with Kotlin
All HSDP service endpoints are documented in the OpenAPI (Swagger) YAML format.
Using these definitions, we generate a lot of boilerplate code during a build,
which allows us to be 'in sync' with the cloud services in production. Our team
has created the Kotlin codegen module for the popular OpenAPI Generator,
which we are contributing to on Github. The code we generate with this includes all
the necessary data transfer objects and classes that wrap the HTTP request/response
handling using ktor. This means that we can focus on writing the business logic that
builds on top of the generated code, instead of spending extra time working on the
plumbing. The moment a production service is upgraded to a new version, we re-generate
the code for it using the updated
YAML file and we can immediately spot the changes we
need to make.
Designing an API for Java, Swift, and Kotlin
An interesting aspect of writing a Kotlin Multiplatform SDK component is thinking about your API. Each platform language has its own paradigms. For instance, asynchronous operations are handled slightly differently in each case. In Java it is natural to invoke an asynchronous method with a callback argument to handle the result, for example:
In Swift, we are used to using completion handlers in this type of scenario. For example:
And finally, for Kotlin users we'd like to offer an API with the suspend keyword,
so they can make use of it using coroutines. One problem that quickly arose with
this was that defining multiple method signatures on the API would generate more
than is needed on each platform. Suspending functions are for instance converted
for Java, but especially on Android SDK levels below 24 (which don't have support
for all Java 8 language features), the result generates quite a verbose signature
that is hard to work with. To address this situation for all target languages, we
added functions with specialized signatures for Java, Kotlin, and Swift on the
external API. These signatures wrap the
suspend variant using a custom
wrapper function that arranges the correct
executing suspending functions. The
@JvmSynthetic annotation was key in hiding the
incompatible Java variants.
This enables us to offer the API function signatures that feel most natural on each native platform while keeping the business logic concentrated under one implementation.
Why did your team decide to use Kotlin Multiplatform?
We had been busy building SDK components for Android and iOS for a while, and cross-platform code reuse was not really happening apart from a few bits and pieces in C, such as the implementation of the SSDP protocol. However, because of the difficulty of bridging between Java, Objective-C, and C, we actually ended up reimplementing this protocol natively on each platform.
This meant that the development team was also split in two when we were implementing new features. The advent of Kotlin Multiplatform provided a serious opportunity to not only become faster at implementing new features, but also to get more interaction in the team between Android and iOS developers.
We knew that Kotlin Multiplatform would still evolve a lot, but the benefits of being able to write once, test once, and deploy more as a result, were compelling enough.
Some of the things that we learned from this experience were:
- The integration of the Kotlin Multiplatform plugin in the IDE (IntelliJ IDEA/Android Studio) has not always been ideal, but it has definitely improved with each release.
- There is always a trade-off between code reuse and writing stuff natively, but this holds true for every cross-platform solution. You have to think hard about which types of business logic can be converged and which should remain native.
- Adopting Kotlin/Native on iOS is definitely harder than using it on Android or JVM, especially when it comes to IDE and debugging support.
- With Kotlin Multiplatform, our development team is able to push out new features much faster, the codebase is easier to maintain.
- The Kotlin language itself helps us write better code with less effort.
- We have seen an increase in interaction and knowledge sharing between the Android and iOS developers in the team.
- Leaving the ecosystem to the community allows JetBrains to focus on the essentials and move faster.
We are planning to target Linux natively as well, so we will have lots of opportunities to see whether the shared codebase pays off. For anyone thinking about using Kotlin Multiplatform, my suggestion would be to look at that portion of your codebase which does not directly interact with platform-native APIs or interfaces that are specific to one platform. Focus on the business logic that has a clear overlap between platforms, since that is where you can actually gain the most.
Jeroen Brosens, Mobile Connectivity Architect, Philips Innovation Services