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 some code in the projects is cross, but some other is platform specific, so will not be shared and there may be a version of this code for any or all platforms. Things like separating the platform specific implementation to another repo in order to reuse them in a future project. Well, lucky you, this page covers that topic.
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 invoke `UserService.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 like `UserService.findAll()`, it needs to do an HTTP call where the actual implementation of sending an HTTP request is different across platforms. While using `project platforms`, this `project lib` can purely focus on the core business logic.
- 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 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.
It should contain the platform implementation and the related tests.
The folder structure is straightforward:
- common: for `common` code which contains all the `expect class`.
- jvm: contains `actual class` implementation for a jvm
- js: contains `actual class` implementation for a js
3. Setup the project lib
The folder structure is like:
- common: business logic that we want to share across platforms
- jvm: Where we actually test and build the `common` for the `jvm` platform.
- js: Where we actually test and build the `common` for the `js` platform.
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 from `project 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 the `common` to yield a platform-specific package.
First, let’s solve them one by one:
1. Add `common` part as dependencies, I tried many ways, including adding the generated `jar` file or `sources-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 you `sourceSets`: `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.
2. 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 are `common` code, which platform for them to run?
3. This is where the `bundle` coming into play. For building and testing. Let’s take `jvm` 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 a `build.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, both `common` modules from `project platforms` and this `project lib`, the source code of platform implementation from `project platforms`.
- You need to add the tests only from `common` module of `project lib`, such that you can run the tests. And it won’t run the tests from `project platforms`, because they all gonna take care there. You don’t need to worry about that. Now run the `gradle :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. 🙂