Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/build-desktop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Build desktop app

on:
push:
branches:
- kotlin-toolchain
pull_request:

jobs:
test:
name: Build
runs-on: ubuntu-latest
# Only run build in template repo
if: github.event.repository.name == 'KMP-App-Template' && github.repository_owner == 'Kotlin'
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Desktop debug build
run: ./kotlin build -m desktopApp
19 changes: 19 additions & 0 deletions .github/workflows/build-web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Build web app

on:
push:
branches:
- kotlin-toolchain
pull_request:

jobs:
test:
name: Build
runs-on: ubuntu-latest
# Only run build in template repo
if: github.event.repository.name == 'KMP-App-Template' && github.repository_owner == 'Kotlin'
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Web debug build
run: ./kotlin build -m webApp -v debug
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@
[![official project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

This is a basic Kotlin Multiplatform app template for Android and iOS. It includes shared business logic and data handling, and a shared UI implementation using Compose Multiplatform.
This is a basic Kotlin Multiplatform app template for Android, iOS, desktop, and web. It includes shared business logic and data handling, and a shared UI implementation using Compose Multiplatform.

> The template is also available [with native UI written in Jetpack Compose and SwiftUI](https://github.com/kotlin/KMP-App-Template-Native).

![Screenshots of the app](images/screenshots.png)

### Running the apps

Use the run configurations provided by the run widget in your IDE's toolbar (make sure that you have the [Kotlin Multiplatform plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform) installed.

You can also use these commands and options:

- Android app: `./kotlin run -m androidApp`
- Desktop app: `./kotlin run -m desktopApp --compose-hot-reload-mode`
- Web app: `./kotlin run -m webApp`
- iOS app: `./kotlin run -m iosApp -p iosSimulatorArm64`, or open the iOS project in Xcode and run it from there.

### Technologies

The data displayed by the app is from [The Metropolitan Museum of Art Collection API](https://metmuseum.github.io/).
Expand Down
2 changes: 0 additions & 2 deletions androidApp/module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ dependencies:
- $libs.ktor.client.okhttp

settings:
kotlin:
version: 2.4.10 # TODO remove once the default catches up
compose:
enabled: true
android:
Expand Down
9 changes: 9 additions & 0 deletions desktopApp/module.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
product: jvm/app

dependencies:
- ../shared
- $compose.desktop.currentOs
- $libs.kotlinx.coroutines.swing

settings:
compose: enabled
18 changes: 18 additions & 0 deletions desktopApp/src/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jetbrains.kmpapp

import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.jetbrains.kmpapp.di.initKoin

fun main() {
initKoin()

application {
Window(
onCloseRequest = ::exitApplication,
title = "KMP-App-Template",
) {
App()
}
}
}
14 changes: 7 additions & 7 deletions iosApp/module.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
39328C78C3709E7A6804F01C /* src */ = {
39328C78C3709E7A6804F01C /* PBXFileSystemSynchronizedRootGroup */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
39328B7F9944E62EB90E46E2 /* PBXFileSystemSynchronizedBuildFileExceptionSet */,
Expand Down Expand Up @@ -53,7 +53,7 @@
children = (
39328689251DF9513C25544F /* Products */,
393286A8F6039FD41E25C11B /* Info.plist */,
39328C78C3709E7A6804F01C /* src */,
39328C78C3709E7A6804F01C /* PBXFileSystemSynchronizedRootGroup */,
);
sourceTree = "<group>";
};
Expand All @@ -64,7 +64,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 393282D7645E8F6DDA49CF5E /* Build configuration list for PBXNativeTarget "app" */;
buildPhases = (
39328FA843134D3FD996EE1F /* Build Kotlin with Amper */,
39328FA843134D3FD996EE1F /* Build Kotlin */,
393282358FF7C02959BEF1C2 /* Sources */,
393283130918FF0FFBC2B306 /* Frameworks */,
39328C7D425627F9B3C28542 /* Resources */,
Expand All @@ -74,7 +74,7 @@
dependencies = (
);
fileSystemSynchronizedGroups = (
39328C78C3709E7A6804F01C /* src */,
39328C78C3709E7A6804F01C /* PBXFileSystemSynchronizedRootGroup */,
);
name = app;
productName = app;
Expand Down Expand Up @@ -111,17 +111,17 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
39328FA843134D3FD996EE1F /* Build Kotlin with Amper */ = {
39328FA843134D3FD996EE1F /* Build Kotlin */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
inputPaths = (
);
name = "Build Kotlin with Amper";
name = "Build Kotlin";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# !AMPER KMP INTEGRATION STEP!\n# This script is managed by the Kotlin Toolchain, do not edit manually!\n\"${KOTLIN_CLI_WRAPPER_PATH}\" tool xcode-integration\n";
shellScript = "# !KOTLIN INTEGRATION STEP!\n# This script is managed by the Kotlin Toolchain, do not edit manually!\n\"${KOTLIN_CLI_WRAPPER_PATH}\" tool xcode-integration\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
80 changes: 80 additions & 0 deletions iosApp/module.xcodeproj/xcshareddata/xcschemes/app.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "39328BF08DCB6A4CE4331D48"
BuildableName = "iosApp.app"
BlueprintName = "app"
ReferencedContainer = "container:module.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "39328BF08DCB6A4CE4331D48"
BuildableName = "iosApp.app"
BlueprintName = "app"
ReferencedContainer = "container:module.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "39328BF08DCB6A4CE4331D48"
BuildableName = "iosApp.app"
BlueprintName = "app"
ReferencedContainer = "container:module.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
2 changes: 0 additions & 2 deletions iosApp/module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ dependencies:
- ../shared

settings:
kotlin:
version: 2.4.10 # TODO remove once the default catches up
compose: enabled
4 changes: 2 additions & 2 deletions kotlin
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
set -e -u

# The version of the Kotlin Toolchain (and CLI) distribution to provision and use
kotlin_cli_version=0.11.1
kotlin_cli_version=0.12.0
# Establish chain of trust from here by specifying exact checksum of Kotlin Toolchain (and CLI) distribution to be run
kotlin_cli_sha256=0ded2a434f6bf193b24e2a6d56c3ba443f4232721155a65aaa8372789412112f
kotlin_cli_sha256=626b2d6e8753ad75b4b02144e597d2f441dd940794be5103352af2e800e4e13e

KOTLIN_CLI_DOWNLOAD_ROOT="${KOTLIN_CLI_DOWNLOAD_ROOT:-https://packages.jetbrains.team/maven/p/amper/amper}"

Expand Down
17 changes: 12 additions & 5 deletions kotlin.bat
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
setlocal

@rem The version of the Kotlin Toolchain distribution to provision and use
set kotlin_cli_version=0.11.1
set kotlin_cli_version=0.12.0
@rem Establish chain of trust from here by specifying the exact checksum of the Kotlin Toolchain distribution to be run
set kotlin_cli_sha256=0ded2a434f6bf193b24e2a6d56c3ba443f4232721155a65aaa8372789412112f
set kotlin_cli_sha256=626b2d6e8753ad75b4b02144e597d2f441dd940794be5103352af2e800e4e13e

if not defined KOTLIN_CLI_DOWNLOAD_ROOT set KOTLIN_CLI_DOWNLOAD_ROOT=https://packages.jetbrains.team/maven/p/amper/amper
if not defined KOTLIN_CLI_BOOTSTRAP_CACHE_DIR set KOTLIN_CLI_BOOTSTRAP_CACHE_DIR=%LOCALAPPDATA%\JetBrains\Kotlin\cli
Expand Down Expand Up @@ -175,7 +175,7 @@ if exist "%wrapper_candidate%" (

if exist "%project_dir%\project.yaml" (
@rem Found project.yaml but no wrapper alongside it
echo WARNING: Found a project.yaml in '%project_dir%', but the wrapper script is missing; using Kotlin Toolchain v$kotlin_cli_version. >&2
echo WARNING: Found a project.yaml in '%project_dir%', but the wrapper script is missing; using Kotlin Toolchain v%kotlin_cli_version%. >&2
exit /b 1
)

Expand Down Expand Up @@ -253,5 +253,12 @@ if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
rem We use busybox here because it doesn't reinterpret the user-passed command-line arguments (that we pass via %*).
rem Also this way we can use the unified launcher script (.sh)
set KOTLIN_CLI_WRAPPER_PATH=%~f0
"%busybox_exe%" sh "%kotlin_cli_target_dir%\bin\launcher.sh" %*
exit /B %ERRORLEVEL%

rem The '& call' after the app run is to avoid the "Terminate batch job (Y/N)?" prompt
"%busybox_exe%" sh "%kotlin_cli_target_dir%\bin\launcher.sh" %* & call :exitWithErrorLevel

:exitWithErrorLevel
@rem We use "%COMSPEC%" /d /c exit so that and/or operators work properly when calling kotlin.bat directly from a script
@rem without the `call` command. With a plain `exit /B %ERRORLEVEL%`, using `kotlin.bat && echo success` would print
@rem 'success' even in case of failure. Consumers would have to use `call kotlin.bat && echo success` for it to work.`
"%COMSPEC%" /d /c exit %ERRORLEVEL%
1 change: 1 addition & 0 deletions libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", ver
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "compose-material-icons" }
Expand Down
2 changes: 2 additions & 0 deletions project.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
modules:
- androidApp
- desktopApp
- iosApp
- shared
- webApp
9 changes: 7 additions & 2 deletions shared/module.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
product:
type: kmp/lib
platforms: [jvm, android, iosArm64, iosSimulatorArm64]
platforms: [jvm, android, iosArm64, iosSimulatorArm64, wasmJs]

dependencies:
- $compose.runtime
Expand All @@ -18,17 +18,22 @@ dependencies:
- $libs.material.icons.core
- $libs.navigation.compose

dependencies@jvm:
- $libs.ktor.client.okhttp

dependencies@android:
- $libs.ktor.client.okhttp

dependencies@ios:
- $libs.ktor.client.darwin

dependencies@wasmJs:
- $libs.ktor.client.js

settings:
compose:
enabled: true
resources:
packageName: kmp_app_template.shared.generated.resources
kotlin:
version: 2.4.10 # TODO remove once the default catches up
serialization: json
8 changes: 8 additions & 0 deletions shared/src/screens/PlaceholderColor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.jetbrains.kmpapp.screens

import androidx.compose.ui.graphics.Color

fun placeholderColor(objectId: Int): Color {
val hue = (objectId * 137).mod(360)
return Color.hsl(hue.toFloat(), saturation = 0.5f, lightness = 0.75f)
}
4 changes: 2 additions & 2 deletions shared/src/screens/detail/DetailScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
Expand All @@ -37,6 +36,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.jetbrains.kmpapp.data.MuseumObject
import com.jetbrains.kmpapp.screens.EmptyScreenContent
import com.jetbrains.kmpapp.screens.placeholderColor
import kmp_app_template.shared.generated.resources.Res
import kmp_app_template.shared.generated.resources.back
import kmp_app_template.shared.generated.resources.label_artist
Expand Down Expand Up @@ -98,7 +98,7 @@ private fun ObjectDetails(
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
.background(placeholderColor(obj.objectID))
)

SelectionContainer {
Expand Down
Loading
Loading