How to separate platform api from kotlin multi-platform project
Previously, we talked about how to share code across platforms in kotlin world. But as your code base grows, you will encounter a scenario where you might want decouple the code. Things like separating the platform specific implementation to another repo in order to reuse them in a future project. Well, lucky you, this is our topic today.
1. Overall structure
- Project platforms
- This repo contains the platform implementation. The repo provides you with a universal API across platforms as well as the platform implementation.
- This is the code base where you need to implement each API twice or thirds depends on how many platforms you want to support.
- Project lib
- This is the
commoncode which you want to share across all platforms without any change. Such that, you can invokeUserService.findAll()to retrieve all the users no matter which platform you are working on. - This code will rely on
project platforms. For instance, an API likeUserService.findAll(), it needs to do an HTTP call where the actual implementation of sending an HTTP request is different across platforms. While usingproject platforms, thisproject libcan purely focus on the core business logic.
- This is the
- Project app
- This is could be a native app which will call
project libfor preparing all the data, while it only needs to focus on the UI and UX. Or maybe some native platform-specific feature.
- This is could be a native app which will call
This is a reasonable approach, at least to me. Where every project could focus on their own business.
2. Setup the project platforms
This is the easiest one. Because it’s just a typical kotlin multi-platform project setup. You can read the official document for more detail. And you see my blog if you want to do code sharing between iOS and Android.
It should contain the platform implementation and the related tests.
The folder structure is straightforward:
- common: for
commoncode which contains all theexpect class. - platforms:
- jvm: contains
actual classimplementation for a jvm - js: contains
actual classimplementation for a js - …
- jvm: contains
3. Setup the project lib
The folder structure is like:
- common: business logic that we want to share across platforms
- bundle
- jvm: Where we actually test and build the
commonfor thejvmplatform. - js: Where we actually test and build the
commonfor thejsplatform. - …
- jvm: Where we actually test and build the
This is one is a little bit tricky because we want to make it common. To things needs to take care here are:
- We need to add the
commonpart fromproject platformsas dependencies. - We want to test this
commonbusiness logic. - When we compile, we need to combine the platform implementations from
project platformsalong with thecommonto yield a platform-specific package.
First, let’s solve them one by one:
- Add
commonpart as dependencies, I tried many ways, including adding the generatedjarfile orsources-jarfile. Always something wrong here, either the auto-completion stops works, or the internal member can’t be recognized while the namespace could be recognized. Only 2 ways work:
- Just embed the source code from
project platforms :commonin yousourceSets:main.kotlin.srcDirs += "$kotlin_idiom_path/interfaces/main" - Add that common module as a subproject and add it to the dependencies.
- I prefer the 1st one because now your gradle settings won’t contain any noises. It will benefit the side-panel of gradle in IDEA as well.
In terms of the test, you just write the tests in this
commonfolder. But you can’t run the tests, you need to run them against the certain platform, otherwise, they arecommoncode, which platform for them to run?This is where the
bundlecoming into play. For building and testing. Let’s takejvmfor example.
- First of all, you don’t need to add any code here, this folder, as its name indicates, just for bundling code together. So, under in
:bundle:jvmsubproject, it only contains abuild.gradlefile. - You need to use
kotlin-platform-jvmto compile this module - In the
sourceSetssetting: you need to add the source code from all the 3 places, bothcommonmodules fromproject platformsand thisproject lib, the source code of platform implementation fromproject platforms. - You need to add the tests only from
commonmodule ofproject lib, such that you can run the tests. And it won’t run the tests fromproject platforms, because they will be taken care there. You don’t need to worry about that. Now run thegradle :bundle:jvm testwill run the tests. - Why I add the source code rather than use the
jarfile? Well, hard lessons learned, this is the only way currently.
4. The end
Now run the :bundle:jvm build, it will build a lib to that platform. Try to consume it, it works really well. :) If you want to know how to make :bundle:ios, :bundle:js, just see my blog.
Thanks for reading!
Follow me (albertgao) on twitter, if you want to hear more about my interesting ideas.