This tutorial gives a short introduction to Amper and how to create a new project.
If you are looking for more detailed info, check the documentation.
Check the setup instructions.
The first thing you’d want to try when getting familiar with a new tool is just a simple "Hello, World" application. Here is what we do:
Create a module.yaml
file:
product: jvm/app
And add some code in the src/
folder:
|-src/
| |-main.kt
|-module.yaml
main.kt
file:
fun main() {
println("Hello, World!")
}
You also need to add a couple of shell scripts to your project folder. Copy the following files from a template project:
|-src/
| |-main.kt
|-module.yaml
|-amper
|-amper.bat
To use Amper in a Gradle-based project, instead of the
amper
andamper.bat
files you need to createsettings.gradle.kts
and Gradle wrappers in the project root. These files are necessary to configure and launch Gradle. Copy the following files from a template project:
- settings.gradle.kts,
- gradlew and gradlew.bat,
- gradle folder
|-gradle/... |-src/ | |-main.kt |-module.yaml |-settings.gradle.kts |-gradlew |-gradlew.bat
That’s it, we’ve just created a simple JVM application.
And since it’s a JVM project, you can add Java code. Java and Kotlin files can reside together,
no need to create separate Maven-like java/
and kotlin/
folders:
|-src/
| |-main.kt
| |-JavaClass.java
|-module.yaml
Examples: JVM "Hello, World!" (standalone, Gradle-based)
Documentation:
Let's add a dependency on a Kotlin library from the Maven repository:
product: jvm/app
dependencies:
- org.jetbrains.kotlinx:kotlinx-datetime:0.4.0
We can now use this library in the main.kt
file:
import kotlinx.datetime.*
fun main() {
println("Hello, World!")
println("It's ${Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())} here")
}
Documentation:
Now let’s add some tests. Amper configures the testing framework automatically,
we only need to add some test code into the test/
folder:
|-src/
| |-...
|-test/
| |-MyTest.kt
|-module.yaml
class MyTest {
@Test
fun doTest() {
assertTrue(true)
}
}
To add test-specific dependencies, use the dedicated test-dependencies:
section.
This should be very familiar to the Cargo, Flutter and Poetry users.
As an example, let's add a MockK library to the project:
product: jvm/app
dependencies:
- org.jetbrains.kotlinx:kotlinx-datetime:0.4.0
test-dependencies:
- io.mockk:mockk:1.13.10
Examples: JVM "Hello, World!" (standalone, Gradle-based)
Documentation:
Another typical task is configuring compiler settings, such as language level etc. Here is how we do it in Amper:
product: jvm/app
dependencies:
- org.jetbrains.kotlinx:kotlinx-datetime:0.4.0
test-dependencies:
- io.mockk:mockk:1.13.10
settings:
kotlin:
languageVersion: 1.8 # Set Kotlin source compatibility to 1.8
jvm:
release: 17 # Set the minimum JVM version that the Kotlin and Java code should be compatible with.
Documentation:
Now, let's turn the example into a GUI application. To do that we'll the Compose Multiplatform framework:
product: jvm/app
dependencies:
# ...other dependencies...
# add Compose dependencies
- $compose.foundation
- $compose.material3
- $compose.desktop.currentOs
settings:
# ...other settings...
# enable the Compose framework toolchain
compose:
enabled: true
and add the following code in the main.kt
file:
import androidx.compose.foundation.text.BasicText
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
BasicText("Hello, World!")
}
}
Now we have a GUI application!
Examples:
- Compose Desktop (standalone, Gradle-based)
- Compose Android (standalone, Gradle-based)
- Compose iOS (standalone, Gradle-based)
- Compose Multiplatform (standalone, Gradle-based)
Documentation:
Let's split our project into a JVM application and a library module with a shared code, which we are going to reuse. It will have the following structure:
|-jvm-app/
| |-src/
| | |-main.kt
| |-test/
| | |-...
| |-module.yaml
|-shared/
| |-src/
| | |-hello.kt
| |-module.yaml
We also add project.yaml
file in the root, next to the existing amper
and amper.bat
files.
It will help Amper find all modules in the project:
|-jvm-app/
| |-...
| |-module.yaml
|-shared/
| |-...
| |-module.yaml
|-amper
|-amper.bat
|-project.yaml
After that add the module to the project.yaml
file:
modules:
- ./jvm-app
- ./shared
Read more about the project layout
In the case of a Gradle-based project,
settings.gradle.kts
is used instead ofproject.yaml
file. So all previously added Gradle files will remain in the project root:|-jvm-app/ | |-... | |-module.yaml |-shared/ | |-... | |-module.yaml |-gradle/... |-settings.gradle.kts |-gradlew |-gradlew.bat
Add modules to the
settings.gradle.kts
file:// ... existing code in the settings.gradle.kts file ... // add new modules to the project include("jvm-app", "shared")Read more about the project layout
The jvm-app/module.yaml
will look like this
product: jvm/app
dependencies:
- ../shared # use the 'shared' module as a dependency
settings:
compose:
enabled: true
Note how a dependency on the shared
module is declared using a relative path.
And the shared/module.yaml
:
product:
type: lib
platforms: [jvm]
dependencies:
- $compose.foundation: exported
- $compose.material3: exported
- $compose.desktop.currentOs: exported
settings:
compose:
enabled: true
Note how the library 'exports' its dependencies. The dependent module will 'see' these dependencies and don't need to explicitly depend on them.
Let's extract the common code into the shared/src/hello.kt
file:
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
@Composable
fun sayHello() {
BasicText("Hello, World!")
}
And re-use it in the jvm-app/src/main.kt
file:
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
sayHello()
}
}
Now we have a multi-module project with some neatly extracted shared code.
Examples: Compose Multiplatform (standalone, Gradle-based)
Documentation:
So far we've been working with a JVM platform to create a desktop application. Let's add an Android and an iOS application. It will be straightforward, since we've already prepared a multi-module layout with a shared module that we can reuse.
Here is the project structure that we need:
|-android-app/
| |-src/
| | |-main.kt
| | |-AndroidManifest.xml
| |-module.yaml
|-ios-app/
| |-src/
| | |-iosApp.swift
| | |-main.kt
| |-module.yaml
|-jvm-app/
| |-...
|-shared/
| |-...
Don't forget to add the new modules into the project.yaml
file:
modules:
- ./android-app
- ./ios-app
- ./jvm-app
- ./shared
In case of a Gradle-based Amper project, into the
settings.gradle.kts
file:// add new modules to the project include("android-app", "ios-app", "jvm-app", "shared")
The android-app/module.yaml
will look like this way:
product: android/app
dependencies:
- ../shared
settings:
compose:
enabled: true
And the ios-app/module.yaml
:
product: ios/app
dependencies:
- ../shared
settings:
compose:
enabled: true
ios:
teamId: <your team ID here> # See https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/
Let's update the shared/module.yaml
and add the new platforms and a couple of additional dependencies for Android:
product:
type: lib
platforms: [ jvm, android, iosArm64, iosSimulatorArm64, iosX64 ]
dependencies:
- $compose.foundation: exported
- $compose.material3: exported
dependencies@jvm:
- $compose.desktop.currentOs: exported
dependencies@android:
# Compose integration with Android activities
- androidx.activity:activity-compose:1.7.2: exported
- androidx.appcompat:appcompat:1.6.1: exported
settings:
compose:
enabled: true
Note how we used the dependencies@jvm:
and dependencies@android:
sections to specify JVM- and Android-specific dependencies.
These dependencies will be added to the JVM and Android versions of the shared
library correspondingly.
They will also be available for the jvm-app
and android-app
modules, since they depend on the shared
module.
Read more about multiplatform configuration in the documentation.
Now, as we have the module structure, we need to add platform-specific application code to the Android and iOS modules.
Create a MainActivity.kt
file in android-app/src
with the following content:
package hello.world
import sayHello
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
sayHello()
}
}
}
Next, create a ViewController.kt
file in ios-app/src
:
import sayHello
import androidx.compose.ui.window.ComposeUIViewController
fun ViewController() = ComposeUIViewController {
sayHello()
}
And the last step, copy
the AndroidManifest.xml file from an example project
into android-app/src
folder, and the iosApp.swift file into the ios-app/src
.
These files bind the Compose UI code with the native application entry points.
Make sure that your project structure looks like this:
|-android-app/
| |-src/
| | |-main.kt
| | |-AndroidManifest.xml
| |-module.yaml
|-ios-app/
| |-src/
| | |-iosApp.swift
| | |-main.kt
| |-module.yaml
|-jvm-app/
|-shared/
|-...
In the case of a Gradle-based project, you also need to add a couple of configuration files. Copy the gradle.properties into your project root, and create a
local.properties
file nearby with the follwoing content:## This file must *NOT* be checked into Version Control Systems sdk.dir=<path to the Android SDK>Check the instructions on how to set the
sdk.dir
on StackOverflowYour project root content will look like this:
|-android-app/ |-... |-settings.gradle.kts |-gradle.properties |-local.properties
Now you can build and run both apps using the Fleet run configurations.
Examples: Compose Multiplatform (standalone, Gradle-based)
Documentation:
You might have noticed that there are some settings present in the module.yaml
files. To redce duplication we can extract them into a template.
Let's create a couple of <name>.module-template.yaml
files:
|-android-app/
| |-...
|-ios-app/
| |-...
|-jvm-app/
| |-...
|-shared/
| |-...
|-compose.module-template.yaml
|-app.module-template.yaml
A /compose.module-template.yaml
with settings common to all modules:
settings:
compose:
enabled: true
and /app.module-template.yaml
with dependencies that are used in the application modules:
dependencies:
- ./shared
Now we will apply these templates to our module files:
/shared/module.yaml
:
product:
type: lib
platforms: [ jvm, android, iosArm64, iosSimulatorArm64, iosX64 ]
apply:
- ../compose.module-template.yaml
dependencies:
- $compose.foundation: exported
- $compose.material3: exported
dependencies@jvm:
- $compose.desktop.currentOs
dependencies@android:
# Compose integration with Android activities
- androidx.activity:activity-compose:1.7.2: exported
- androidx.appcompat:appcompat:1.6.1: exported
/jvm-app/module.yaml
:
product: jvm/app
apply:
- ../compose.module-template.yaml
- ../app.module-template.yaml
/android-app/module.yaml
:
product: android/app
apply:
- ../compose.module-template.yaml
- ../app.module-template.yaml
/ios-app/module.yaml
:
product: ios/app
apply:
- ../compose.module-template.yaml
- ../app.module-template.yaml
settings:
ios:
teamId: <your team ID here> # See https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/
You can put all common dependencies and settings into the template. It's also possible to have multiple templates for various typical configurations in the project.
Documentation:
Check the documentation and explore examples for the standalone Amper projects and for the Gradle-based Amper projects.