Intro to Multiplatform Projects with Kotlin

Jose Flores
Atlas
Published in
6 min readApr 25, 2018

Multiplatform projects allow you to write common code once and target different platforms like the web, Android or a desktop app. That means you can write your business logic in one place, define platform expectations and implement those expectations in platform-specific ways.

They were introduced in Kotlin 1.2 as an experimental feature. Initially it had support for targeting the JVM and JavaScript as Platform Types but with the recent release of 1.2.30, Android was introduced as a Platform type.

apply plugin: ‘kotlin-platform-androiddependencies {
expectedBy project(“:multiplatform-app”)

Multi Module Projects ?

The modules you would normally use for multi module projects can still be used but are referred to as regular modules in the context of a multiplatform project.

A multiplatform project can consist of:

  • A common module contains code that is not specific to any platform and can make declarations without implementations.
  • A platform module contains implementations of platform-dependent declarations
  • A regular module can target a specific platform modules.

The biggest thing to note is that a common module has code that is not specific to any platform. It can depend on modules and libraries only if they are common. A common version of the Kotlin standard library is included as a dependency by default in an IntelliJ multiplatform project.

For the most part, you can treat platform modules as regular modules, that is you can depend on any library that suits your needs without any additional constraints. With the exception that platform modules are expected to provide actual implementations that are defined by the common module.

dependencies {
expectedBy project(":multiplatform-common")
}

This gives the common module the ability to define certain expectations that are enforced by the Kotlin compiler.

Kotlin Keywords !

We define this contract using the Kotlin keywords: expect and actual. Again, the common module can only have Kotlin code (common) and you can write a lot of your code without thinking about the target platforms, but you’ll undoubtedly come to a point where you need to interact with something that needs to be defined on the platform side.

Common Expects

Common Module: Platform.kt

Common expects there to be a class named Platform.

That class should have a greetingMethod and name.

Oh and also go ahead and define a top level function that returns an instance of your Platform.

The common module expects there to be an actual Platform implementation, but it doesn’t add too many constraints. The common module could have forced platform types to use a no-argument constructor by simply adding a constructor in the expectation.

expect class Platform() {

This alone only makes the platform types define an actual implementation of a no argument constructor but nothing in our example forces them to use it in the creation. Hypothetically we could have defined a constructor that took in arguments of different types with the intention of creating those objects through that constructor from the common module.

It also allowed platform types to have control over the creation of the instance.

expect fun getPlatform(): Platform

In this case we’re using a simple top level function to accomplish that but even in a more elegant solution the idea is that we can allow or control the level of control we can give to the platform types.

With the example above, we don’t care if the platform types use a no-argument constructor or if they pass in 100 objects to create the Platform type. An implementation in one of the platforms will not affect another platform. We also don’t care how the platform types create the Platform, it can be through a factory, using reflection or simply calling a no-arg constructor and returning the instance.

Anything marked with expect can’t have implementations. So you can’t define a function inside a class marked with the expect keyword. But you can use those declarations as if they were defined.

Common Module: Common.kt

Here, our common code defined a function named commonSharedCode(…) that interacted with a class that it expected (Platform) in an intuitive way. In our main, we called getPlatform() which we expect to be defined by the platform.

Platforms provide actual

Platform JVM Module: Platform.kt

Each platform type now has to define actual implementations and they have to be in the same package that the common module expects them to be. If any expectation is missing the builds will fail.

I was surprised at the syntax here because I had to add actual to the properties and I didn’t have to add expect to them when we defined it.

expect class Platform {
val greetingMethod: String
val name: String
}

This makes sense if we remember that we could not add actual implementations to our expectation in the common module so any function or property defined in the above expectation can only be expect and not actual. On the other hand, in our platform’s actual implementation above you could define regular functions and properties that are not fulfilling any expect/actual contract so you have to add the actual keyword to differentiate from those normal properties.

Output/Artifacts

“A Kotlin multiplatform project allows you to compile the same code to multiple target platforms.”

The common module gets included into the final output of each target platform output. This is the result we were looking for, we wrote common code once, defined platform expectations , and we were able to target native code.

Although this looks similar to what would happen if you had a dependency on a regular module, there are some major distinctions. One of them being the expect and the actual declarations, unsurprisingly, go away in the final output. In a Java project we would only see a single .class file for both the expect and actual .

It makes sense that the expect declaration and the actual get compiled into one class (the requirement to keep them in the same package was a good hint) but what this allows us to do is to trade abstraction layers (abstract classes/interfaces) previously used for a multiplatform solution that can still define contracts and doesn’t leave a footprint in the final output with the added bonus of being able to enforce those contracts.

Summary

In this post, we checked out the new module definitions that can be associated with a Multiplatform project. We explored the mechanism that allows a common module to expect things from a platform module through the new Kotlin keywords expect and actual. We also quickly covered the build output artifacts and some of the differences compared to a normal multiplatform project.

Multiplatform projects provide a solution that allows (promotes) for separation of concerns, replaces abstraction layers with clear declarations and implementations, provides compile time safety and can target platform-specific code (JVM bytecode or JS source code). This barely scratches the surface of what is going to be possible with this solution and the impact that this will have for large multiplatform projects but hopefully this was a helpful introduction.

I wrote a Multiplatform “hello world” tutorial while writing this so it should feel like a natural follow up if you wanted to setup a Multiplatform project.

Multiplatform Projects with Kotlin: JVM, JS, Android Tutorial

Announcements:

Documentation:

Articles:

--

--

Jose Flores
Atlas
Writer for

A passionate Software Engineer with a focus in Android development and a love for solving challenging problems.