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
common
code 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 lib
can purely focus on the core business logic.
- This is the
- Project app
- This is could be a native app which will call
project lib
for 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
common
code which contains all theexpect class
. - platforms:
- jvm: contains
actual class
implementation for a jvm - js: contains
actual class
implementation 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
common
for thejvm
platform. - js: Where we actually test and build the
common
for thejs
platform. - …
- 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
common
part fromproject platforms
as dependencies. - We want to test this
common
business logic. - When we compile, we need to combine the platform implementations from
project platforms
along with thecommon
to yield a platform-specific package.
First, let’s solve them one by one:
- Add
common
part as dependencies, I tried many ways, including adding the generatedjar
file orsources-jar
file. 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 :common
in 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
common
folder. But you can’t run the tests, you need to run them against the certain platform, otherwise, they arecommon
code, which platform for them to run?This is where the
bundle
coming into play. For building and testing. Let’s takejvm
for 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:jvm
subproject, it only contains abuild.gradle
file. - You need to use
kotlin-platform-jvm
to compile this module - In the
sourceSets
setting: you need to add the source code from all the 3 places, bothcommon
modules fromproject platforms
and thisproject lib
, the source code of platform implementation fromproject platforms
. - You need to add the tests only from
common
module 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 test
will run the tests. - Why I add the source code rather than use the
jar
file? 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.