Create your first cross-platform app
Here you will learn how to create and run your first Kotlin Multiplatform application using Android Studio.
Kotlin Multiplatform technology simplifies the development of cross-platform projects. Kotlin Multiplatform applications can work on a variety of platforms like iOS, Android, macOS, Windows, Linux, web, and others.
One of the major Kotlin Multiplatform use cases is sharing code between mobile platforms. You can share application logic between iOS and Android apps and write platform-specific code only when you need to implement a native UI or work with platform APIs.
Set up the environment
Check out the article about setting up an environment for Kotlin Multiplatform development, if you haven't already. Make sure that you have:
Installed the Kotlin Multiplatform plugin for Android Studio.
Launched Xcode at least once and accepted the terms of use if you plan to build iOS apps.
Run Kdoctor to check for any issues in the setup.
The following instructions assume that you have all software necessary for the platforms you're aiming at.
Create the project with a wizard
Open the Kotlin Multiplatform wizard.
On the New project tab, change the project name to "GreetingKMP" and the project ID to "com.jetbrains.greeting".
Ensure that the Android and iOS options are selected.
For iOS, select the Do not share UI option to keep the UI native.
Click the Download button and unpack the resulting archive.
Examine the project structure
Launch Android Studio.
On the Welcome screen, click Open, or select File | Open in the editor.
Navigate to the unpacked project folder and then click Open.
Android Studio detects that the folder contains a Gradle build file and opens the folder as a new project.
The default view in Android Studio is optimized for Android development. To see the full file structure of the project, which is more convenient for multiplatform development, switch the view from Android to Project:
Each Kotlin Multiplatform project includes three modules:
shared is a Kotlin module that contains the logic common for both Android and iOS applications – the code you share between platforms. It uses Gradle as the build system to help automate your build process.
composeApp is a Kotlin module that builds into an Android application. It uses Gradle as the build system. The composeApp module depends on and uses the shared module as a regular Android library.
iosApp is an Xcode project that builds into an iOS application. It depends on and uses the shared module as an iOS framework. The shared module can be used as a regular framework or as a CocoaPods dependency. By default, the Kotlin Multiplatform wizard creates projects that use the regular framework dependency.
The shared module consists of three source sets: androidMain
, commonMain
, and iosMain
. Source set is a Gradle concept for a number of files logically grouped together where each group has its own dependencies. In Kotlin Multiplatform, different source sets in a shared module can target different platforms.
The common source set contains shared Kotlin code, and platform source sets use Kotlin code specific to each target. Kotlin/JVM is used for androidMain
and Kotlin/Native for iosMain
:
When the shared module is built into an Android library, common Kotlin code is treated as Kotlin/JVM. When it is built into an iOS framework, common Kotlin is treated as Kotlin/Native:
Write common declarations
The common source set contains shared code that can be used across multiple target platforms. It's designed to contain code that is platform-independent. If you try to use platform-specific APIs in the common source set, the IDE will show a warning:
Open the
Greeting.kt
file and try to access one of the Java classes,java.util.Random().nextBoolean()
, inside thegreet()
function:import java.util.Random fun greet(): String { val firstWord = if (Random().nextBoolean()) "Hi!" else "Hello!" return firstWord }Android Studio highlights that the
Random
class is unresolved because you can't call specific Java functions from the common Kotlin code.Follow the IDE's suggestions and replace it with
kotlin.random.Random
from the Kotlin standard library. This is a multiplatform library that works on all platforms and is included automatically as a dependency.Remove brackets from
Random()
, as it is an abstract class. The code should now compile successfully.Add a bit of variety to the greeting. Update the shared code with the
reversed()
function from the Kotlin standard library to reverse the text:import kotlin.random.Random class Greeting { private val platform: Platform = getPlatform() fun greet(): String { val firstWord = if (Random.nextBoolean()) "Hi!" else "Hello!" return "$firstWord Guess what this is! > ${platform.name.reversed()}!" } }
Writing the code only in common Kotlin has obvious limitations because it can't use any platform specifics. Using interfaces and the expect/actual mechanism solves this.
Check out platform-specific implementations
The common source set can define an interface or an expected declaration. Then each platform source set, in this case androidMain
and iosMain
, has to provide actual platform-specific implementations for the expected declarations from the common source set.
While generating the code for a specific platform, the Kotlin compiler merges expected and actual declarations and generates a single declaration with actual implementations.
When creating a project with the web wizard or using the Kotlin Multiplatform plugin in Android Studio, you get a template with the
Platform.kt
file in thecommonMain
module:interface Platform { val name: String }It's a common
Platform
interface with information about the platform.Switch between the
androidMain
and theiosMain
modules. You'll see that they have different implementations of the same functionality for the Android and the iOS source sets:// Platform.android.kt in the androidMain module: class AndroidPlatform: Platform { override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}" }// Platform.ios.kt in the iosMain module: import platform.UIKit.UIDevice class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion }The
name
property implementation fromAndroidPlatform
uses the Android platform code, namely theandroid.os.Build
dependency. This code is written in Kotlin/JVM. If you try to accessjava.util.Random
here, this code will compile.The
name
property implementation fromIOSPlatform
uses iOS platform code, namely theplatform.UIKit.UIDevice
dependency. It's written in Kotlin/Native, meaning you can write iOS code in Kotlin. This code becomes a part of the iOS framework, which you will later call from Swift in your iOS application.
Check the
getPlatform()
function in different source sets. Its expected declaration doesn't have a body, and actual implementations are provided in the platform code:// Platform.kt in the commonMain module: expect fun getPlatform(): Platform// Platform.android.kt in the androidMain module: actual fun getPlatform(): Platform = AndroidPlatform()// Platform.ios.kt in the iosMain module: actual fun getPlatform(): Platform = IOSPlatform()
Here, the common source set defines an expected getPlatform()
function and has actual implementations, AndroidPlatform()
for the Android app and IOSPlatform()
for the iOS app, in the platform source sets.
While generating the code for a specific platform, the Kotlin compiler merges expected and actual declarations into a single getPlatform()
function with its actual implementations.
That's why expected and actual declarations should be defined in the same package − they are merged into one declaration in the resulting platform code. Any invocation of the expected getPlatform()
function in the generated platform code calls a correct actual implementation.
Now you can run the apps and see all of this in action.
Explore the expect/actual mechanism (optional)
The template project uses the expect/actual mechanism for functions, but it also works for most Kotlin declarations, such as properties and classes. Let's implement an expected property:
Open
Platform.kt
in thecommonMain
module and add the following at the end of the file:expect val num: IntThe Kotlin compiler complains that this property has no corresponding actual declarations in the platform modules.
Try to provide the implementation right away with:
expect val num: Int = 42You'll get an error saying that expected declarations must not have a body, in this case an initializer. The implementations must be provided in actual platform modules. Remove the initializer.
Select the
num
property. Press Option + Enter and choose "Add missing actual declarations". Choose theandroidMain
source set. You can then complete the implementation inandroidMain/Platform.android.kt
:actual val num: Int = 1Now provide the implementation for the
iosMain
module. Add the following toiosMain/Platform.ios.kt
:actual val num: Int = 2Add the
num
property to thegreet()
function to see the differences:fun greet(): String { val firstWord = if (Random.nextBoolean()) "Hi!" else "Hello!" return "$firstWord [$num] Guess what this is! > ${platform.name.reversed()}!" }
Run your application
You can run your multiplatform application for both Android or iOS from Android Studio.
Run your application on Android
In the list of run configurations, select composeApp.
Choose an Android virtual device next to the list of configurations and click Run.
If you don't have a device in the list, create a new Android virtual device.
Run on a different Android simulated device
Run on a real Android device
Run your application on iOS
Launch Xcode in a separate window. The first time you do this, you may also need to accept the license terms and allow Xcode to perform some necessary initial tasks.
In Android Studio, select iosApp in the list of run configurations and click Run.
If you don't have an available iOS configuration in the list, add a new run configuration.
Run on a new iOS simulated device
If you want to run your application on a simulated device, you can add a new run configuration.
In the list of run configurations, click Edit Configurations.
Click the + button above the list of configurations and then select iOS Application.
Name your configuration.
Select the Xcode project file. To do so, navigate to your project, for example, KotlinMultiplatformSandbox, open the
iosApp
folder, and then select the.xcodeproj
file.In the Execution target list, select a simulated device and then click OK.
Click Run to run your application on the new simulated device.
Run on a real iOS device
You can run your multiplatform application on a real iOS device. Before you start, you'll need to set the Team ID associated with your Apple ID.
Set your Team ID
To set the Team ID in your project, you can either use the KDoctor tool in Android Studio or choose your team in Xcode.
For KDoctor:
In Android Studio, run the following command in the terminal:
kdoctor --team-idsKDoctor will list all Team IDs currently configured on your system, for example:
3ABC246XYZ (Max Sample) ZABCW6SXYZ (SampleTech Inc.)In Android Studio, open the
iosApp/Configuration/Config.xcconfig
and specify your Team ID.
Alternatively, choose the team in Xcode:
Go to Xcode and select Open a project or file.
Navigate to the
iosApp/iosApp.xcworkspace
file of your project.In the left-hand menu, select
iosApp
.Navigate to Signing & Capabilities.
In the Team list, select your team.
If you haven't set up your team yet, use the Add an Account option in the Team list and follow Xcode instructions.
Make sure that the Bundle Identifier is unique and the Signing Certificate is successfully assigned.
Run the app
Connect your iPhone with a cable. If you already have the device registered in Xcode, Android Studio should show it in the list of run configurations. Run the corresponding iosApp
configuration.
If you haven't registered your iPhone in Xcode yet, follow Apple recommendations. In short, you should:
Connect your iPhone with a cable.
On your iPhone, enable the developer mode in Settings | Privacy & Security.
In Xcode, go to the top menu and choose Window | Devices and Simulators.
Click on the plus sign. Select your connected iPhone and click Add.
Sign in with your Apple ID to enable development capabilities on the device.
Follow the on-screen instructions to complete the pairing process.
Once you've registered your iPhone in Xcode, create a new run configuration in Android Studio and select your device in the Execution target list. Run the corresponding iosApp
configuration.
Next step
In the next part of the tutorial, you'll learn how to update the UI elements using platform-specific libraries.
See also
See how to create and run multiplatform tests to check that the code works correctly.
Learn more about the project structure.
If you want to convert your existing Android project into a cross-platform app, complete this tutorial to make your Android app cross-platform.
Get help
Kotlin Slack. Get an invite and join the #multiplatform channel.
Kotlin issue tracker. Report a new issue.