Kotlin 1.2 brings with it experimental support for multi-platform projects and last week at KotlinConf we showed how you can now use Kotlin to target the Jvm, the Web, Android and iOS. The source code for the actual KotlinConf is availabe on GitHub, and while it’s a great example of multi-platform project, it’s got a lot of moving pieces and opening it for the first time can be overwhelming.
I’m currently preparing a two week road trip to Asia, and one of the things I’ll be talking about is precisely MPPs. So I’ve prepared a very simple sample project which is now available on GitHub, and in this
blog post I’ll walk you through the different parts.
Project Structure
The project consists of three modules:
- common module for shared code
- js module targeting JavaScript
- jvm module targeting the JVM
Common module
This module contains all common shared code, and should be limited to Kotlin.
In the case of a data classes, usually the implementation is the same
whether targeting the JVM, JavaScript or other platforms. In our case, we have this as part of the package com.hadihariri.multiplatform.common.data
.
expect and actual
There are times when we still need to share common code, but have different behaviours on different platforms, since the implementation touch platform-specific calls and/or libraries.
That’s where the expect and actual keywords come in to play and this is the module where we need to define them. If you recall C/C++, think of them as headers and implementations. We can mark any class or functions with the keyword
expect to indicate that the code is present, but the actual implementation will be provided in each specific module. How we link these modules and make the compiler aware of it all, we’ll see in a bit.
In our case, we’re going to define a class called Date
and a function called platformMessage
, and place these in the com.hadihariri.multiplatform.common
package.
The code itself would be
package com.hadihariri.multiplatform.common
import com.hadihariri.multiplatform.common.data.*
expect class Date() {
fun getDate(): Int
fun getMonth(): Int
fun getFullYear(): Int
fun getHours(): Int
fun getMinutes(): Int
fun getTime(): Number
}
expect fun platformMessage(message: Message)
Again, notice how there’s not implementation yet.
actual implementations
As soon as we do this, IntelliJ IDEA will display an error, along with an Intention, indicating that the implementation for these declarations are missing
We need to now implement these in their corresponding modules. That’s where platform specific modules come in.
Platform specific modules
Since we’re targeting Jvm and JavaScript, we have two platform specific modules. In each of these we’ll have Kotlin code that can talk and interop with platform libraries and languages. In addition we’ll have the actual implementations of our expect declarations declared in the common module.
In our case, this would be the implementations for the Data
class and the platformMessage
function
package com.hadihariri.multiplatform.common
import com.hadihariri.multiplatform.common.data.*
actual fun platformMessage(message: Message) {
println("(JVM) [${message.priority}] ${message.priority}")
}
package com.hadihariri.multiplatform.common
actual class Date {
actual constructor()
actual fun getDate(): Int {
...
}
...
}
One important thing to note here: the code should be declared in the same package as it is defined in the common module. If we see our project structure we can see how we have a common and a jvm package defined in the jvm module
Linking modules with Gradle
One piece missing in this picture is how we link the Gradle projects together so that they correctly identify the dependencies between modules. It’s similar to the compile
directive in Gradle
which allows one project to be referenced by another
dependencies {
compile project(':common')
except in this case, instead of compile
, we use expectedBy
dependencies {
expectedBy project(':common')
Starting a new MP project in IntelliJ IDEA
Currently multi-platform projects are only supported using Gradle. In IntelliJ IDEA, we can create the necessary modules using the New Module wizard
We first start by creating a general Gradle module, and then add a module per target platform (which then defines the correct dependencies). Currently the expectedBy
relationship needs to be set manually.
Once again, the complete source code for the sample project is available on GitHub