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/appAnd 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
amperandamper.batfiles you need to createsettings.gradle.ktsand 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.0We 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.10Examples: 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: trueand 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
- ./sharedRead more about the project layout
In the case of a Gradle-based project,
settings.gradle.ktsis used instead ofproject.yamlfile. 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.batAdd modules to the
settings.gradle.ktsfile:// ... 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: trueNote 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: trueNote 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.ktsfile:// 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: trueAnd 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: trueNote 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.propertiesfile 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.diron 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: trueand /app.module-template.yaml with dependencies that are used in the application modules:
dependencies:
- ./sharedNow 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.