{"id":18418863,"url":"https://github.com/retheviper/kotlinmultiplatformsample","last_synced_at":"2026-04-30T13:34:29.322Z","repository":{"id":102721409,"uuid":"587733805","full_name":"retheviper/KotlinMultiplatformSample","owner":"retheviper","description":"for study","archived":false,"fork":false,"pushed_at":"2026-03-15T06:16:36.000Z","size":32528,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-15T15:51:37.345Z","etag":null,"topics":["compose-desktop","compose-web","ios","jetpack-compose","kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","ktor","ktor-client","ktor-server","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/retheviper.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-11T13:11:04.000Z","updated_at":"2026-03-15T06:16:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"debbd5e1-ba85-4ee0-a165-8c3b5dd40427","html_url":"https://github.com/retheviper/KotlinMultiplatformSample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/retheviper/KotlinMultiplatformSample","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retheviper%2FKotlinMultiplatformSample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retheviper%2FKotlinMultiplatformSample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retheviper%2FKotlinMultiplatformSample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retheviper%2FKotlinMultiplatformSample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/retheviper","download_url":"https://codeload.github.com/retheviper/KotlinMultiplatformSample/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retheviper%2FKotlinMultiplatformSample/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32466333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compose-desktop","compose-web","ios","jetpack-compose","kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","ktor","ktor-client","ktor-server","swiftui"],"created_at":"2024-11-06T04:14:51.393Z","updated_at":"2026-04-30T13:34:29.316Z","avatar_url":"https://github.com/retheviper.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# KotlinMultiplatformSample\n\n[English](./README.md) | [한국어](./README.ko.md) | [日本語](./README.ja.md)\n\nWorkspace-based chat application built with Kotlin Multiplatform.\n\n![Concept diagram](./concept.svg)\n\nThis repository contains:\n\n- a Ktor API server\n- a Compose Multiplatform shared UI\n- a Compose Wasm web client served by the API\n- a Compose Desktop shell\n- a macOS SwiftUI shell\n- an Android shell backed by the shared Compose UI\n- an iOS SwiftUI shell backed by shared contracts and networking\n\nThe current implementation is a Slack-like vertical slice with workspaces, channels, threaded replies, mentions, notifications, reactions, and link previews.\nNotifications are push-driven on Web, Compose Desktop, and the macOS SwiftUI shell.\n\n## Project Structure\n\n```text\napi/\n  Ktor server\n  messaging domain/application/infrastructure/presentation layers\n  Flyway migrations\n  OpenAPI document\n\nshared/\n  shared contracts and client models\n  shared Ktor client\n  shared Compose UI/state/resources\n  Compose Wasm entry point\n\napp/androidApp/\n  Android shell using the shared Compose UI\n\napp/desktopApp/\n  Compose Desktop shell\n\napp/macosApp/\n  macOS SwiftUI shell\n\napp/iosApp/\n  iOS SwiftUI shell\n\ncompose.yaml\n  Local PostgreSQL service definition\n```\n\n## Tech Stack\n\n- Kotlin Multiplatform\n- Ktor 3\n- Compose Multiplatform\n- PostgreSQL\n- R2DBC\n- Exposed\n- Flyway\n- WebSocket chat transport\n- Server-Sent Events for notification refresh\n- Testcontainers for API integration tests\n\n## Platform Status\n\n- Web: implemented and served by the API\n- Desktop: implemented with Compose Desktop\n- macOS native: implemented with SwiftUI in `app/macosApp`\n- Android: implemented with a thin shell in `app/androidApp`, reusing the `shared` Compose UI, state, resources, and networking\n- iOS: implemented with a SwiftUI shell in `app/iosApp`, using `shared` for contracts and networking\n- iPadOS: implemented with the same `app/iosApp` target, with adaptive iPad layouts and simulator support\n\n## Apple Shell Notes\n\n- iOS and macOS now share Swift models/helpers and several SwiftUI components instead of duplicating platform code.\n- The iOS SwiftUI shell is split by concern into root screens, workspace shell, channel/thread screens, message components, and overlay components.\n- New Apple-side work should verify the latest official SwiftUI, WebKit, UserNotifications, Ktor, and Kotlin Multiplatform documentation before implementation when behavior is version-sensitive.\n\n## Prerequisites\n\nRequired:\n\n- JDK 17 or newer\n- Docker with `docker compose`\n\nOptional:\n\n- Android SDK for `:app:androidApp`\n- Xcode and Swift 6 toolchain for `app/macosApp`\n- Xcode with iOS Simulator runtimes for `app/iosApp`\n\nVerified toolchain in this repository:\n\n- Gradle 9.4.0 via wrapper\n\n## Local Run\n\n### 1. Start PostgreSQL\n\n```bash\ndocker compose up -d db\n```\n\nStop it with:\n\n```bash\ndocker compose down\n```\n\nReset local database volume completely:\n\n```bash\ndocker compose down -v\ndocker compose up -d db\n```\n\n### 2. Run the API server\n\n```bash\n./gradlew :api:run\n```\n\nBy default, the server expects the local PostgreSQL instance from `compose.yaml`.\n\nSupported database environment variables:\n\n```bash\nCHAT_JDBC_URL\nCHAT_R2DBC_URL\nCHAT_DB_USER\nCHAT_DB_PASSWORD\n```\n\nExample:\n\n```bash\nexport CHAT_JDBC_URL=jdbc:postgresql://localhost:5432/messaging_app\nexport CHAT_R2DBC_URL=r2dbc:postgresql://localhost:5432/messaging_app\nexport CHAT_DB_USER=postgres\nexport CHAT_DB_PASSWORD=postgres\n./gradlew :api:run\n```\n\n### 3. Run Android\n\nStart the API first, then use the Gradle shortcut task:\n\n```bash\n./gradlew :api:run\n./gradlew :app:listAndroidAvds\n./gradlew :app:runAndroidEmulator -PandroidAvd=\u003cyour-avd-name\u003e\n```\n\nIf you already have exactly one emulator running, `-PandroidAvd=...` is optional. If multiple emulators are connected, pass `-PandroidDeviceSerial=\u003cadb-serial\u003e`.\nIf Android builds fail during the `androidJdkImage` / `jlink` step, run Gradle with a standard JDK such as Temurin 21 instead of GraalVM for this repository.\n\nThe Android shell connects to `http://10.0.2.2:8080` by default so the emulator can reach the host machine.\nIt intentionally keeps platform code thin and delegates product UI, state, resources, and networking to `:shared`.\nIf you launch the emulator from Android Studio instead, start any device from Device Manager before running `installDebug`.\nThe current default networking setup targets the Android emulator. A physical Android device is not configured out of the box because `10.0.2.2` is emulator-only.\n\nExample:\n\n```bash\nJAVA_HOME=/Users/youngbinkim/Library/Java/JavaVirtualMachines/temurin-21.0.9/Contents/Home \\\n./gradlew :app:runAndroidEmulator -PandroidAvd=\u003cyour-avd-name\u003e\n```\n\nWhat happens during startup:\n\n- Flyway applies schema migrations\n- the web frontend bundle is prepared from `shared`\n- the API serves the web client from `/`\n\n### 4. Open the application\n\nDefault endpoints:\n\n- App: [http://localhost:8080/](http://localhost:8080/)\n- Swagger UI: [http://localhost:8080/docs](http://localhost:8080/docs)\n- OpenAPI: [http://localhost:8080/openapi.yaml](http://localhost:8080/openapi.yaml)\n- Health: [http://localhost:8080/health](http://localhost:8080/health)\n- MCP Streamable HTTP: [http://localhost:8080/mcp](http://localhost:8080/mcp)\n\nCurrent MCP tools:\n\n- `get_health`\n- `list_workspaces`\n- `create_workspace`\n- `get_workspace_by_slug`\n- `list_workspace_channels`\n- `create_channel`\n- `list_members`\n- `add_member`\n- `update_member`\n- `list_channel_messages`\n- `get_thread`\n- `post_message`\n- `reply_message`\n- `toggle_reaction`\n- `list_notifications`\n- `mark_notifications_read`\n\n### MCP client examples\n\nMCP Inspector:\n\n```bash\nnpx -y @modelcontextprotocol/inspector\n```\n\nThen select `Streamable HTTP` and connect to `http://localhost:8080/mcp`.\n\nQuick initialization check with `curl`:\n\n```bash\ncurl -i http://localhost:8080/mcp \\\n  -H 'Content-Type: application/json' \\\n  -H 'Accept: application/json, text/event-stream' \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"initialize\",\n    \"params\": {\n      \"protocolVersion\": \"2025-03-26\",\n      \"capabilities\": {},\n      \"clientInfo\": {\n        \"name\": \"manual-check\",\n        \"version\": \"1.0.0\"\n      }\n    }\n  }'\n```\n\nThe response may include an `Mcp-Session-Id` header. Reuse that header on subsequent MCP requests when the client keeps a stateful session.\n\nCLI helper in this repository:\n\n```bash\n./scripts/mcp_cli.py init\n./scripts/mcp_cli.py tools\n./scripts/mcp_cli.py call list_workspaces\n./scripts/mcp_cli.py call create_workspace \\\n  --arg slug=acme \\\n  --arg name=Acme \\\n  --arg ownerUserId=u-alice \\\n  --arg ownerDisplayName=Alice\n```\n\nThe CLI stores the MCP session locally in `.mcp-cli/` and reuses it for later tool calls.\n\n## Frontend Development Notes\n\nThe API serves the web frontend directly. You do not need a separate frontend dev server.\n\nFor faster iteration on the Wasm frontend, run the frontend distribution task continuously in one terminal and the API in another:\n\n```bash\n./gradlew :shared:wasmJsBrowserDevelopmentExecutableDistribution --continuous\n./gradlew :api:run\n```\n\nWhen the Wasm output changes, the running API will serve the updated static files.\n\n## Desktop Run\n\nStart the API first, then launch the desktop shell:\n\n```bash\n./gradlew :api:run\n./gradlew :app:desktopApp:run\n```\n\nThe Compose Desktop client connects to `http://localhost:8080` by default.\nOn macOS, running without an explicit shell selection opens the chooser by default.\nThe Compose Desktop shell exits when the main window is closed.\n\nTo point it at a different server:\n\n```bash\n./gradlew -Dmessaging.baseUrl=http://localhost:8080 :app:desktopApp:run\n```\n\nOn macOS, the desktop launcher can run either the Compose Desktop shell or the SwiftUI native shell.\n\nShell selection options:\n\n```bash\n./gradlew -Dchat.desktop.shell=compose :app:desktopApp:run\n./gradlew -Dchat.desktop.shell=chooser :app:desktopApp:run\n./gradlew -Dchat.desktop.shell=mac-native :app:desktopApp:run\n```\n\n`compose` runs the Compose Desktop shell. On macOS, `mac-native` launches the SwiftUI shell from `app/macosApp`, and `chooser` opens the shell picker explicitly.\n\nYou can also use environment variables instead of JVM system properties:\n\n```bash\nCHAT_DESKTOP_SHELL=mac-native ./gradlew :app:desktopApp:run\nMESSAGING_BASE_URL=http://localhost:8080 ./gradlew :app:desktopApp:run\n```\n\nYou can also run the SwiftUI shell directly:\n\n```bash\nswift run --package-path app/macosApp\n```\n\n## iOS Simulator Run\n\nStart the API first:\n\n```bash\n./gradlew :api:run\n./gradlew :app:listAppleSimulators\n```\n\nThen choose an installed iPhone simulator name and let Gradle build, install, and launch the app:\n\n```bash\n./gradlew :app:runIosSimulator -PiosSimulator=\"\u003cyour-iphone-simulator\u003e\"\n```\n\nTo override the server base URL during simulator launch:\n\n```bash\n./gradlew :app:runIosSimulator \\\n  -PiosSimulator=\"\u003cyour-iphone-simulator\u003e\" \\\n  -Pmessaging.baseUrl=http://localhost:8080\n```\n\nDefault server target for the iOS shell:\n\n```bash\nMESSAGING_BASE_URL=http://localhost:8080\n```\n\nThe iOS shell defaults to `http://localhost:8080`, which works for the iOS Simulator because it shares the Mac host network.\nThe Gradle task forwards `-Pmessaging.baseUrl=...` to the simulator launch as `SIMCTL_CHILD_MESSAGING_BASE_URL`.\n\nIf you prefer Xcode, open `app/iosApp/iosApp.xcodeproj`, choose any installed iPhone Simulator, set `MESSAGING_BASE_URL` in the Run scheme if needed, and run the `iosApp` scheme.\n\n## iPadOS Simulator Run\n\nStart the API first:\n\n```bash\n./gradlew :api:run\n./gradlew :app:listAppleSimulators\n```\n\nThen choose an installed iPad simulator name and let Gradle build, install, and launch the same target:\n\n```bash\n./gradlew :app:runIpadSimulator -PipadSimulator=\"\u003cyour-ipad-simulator\u003e\"\n```\n\nTo override the server base URL during simulator launch:\n\n```bash\n./gradlew :app:runIpadSimulator \\\n  -PipadSimulator=\"\u003cyour-ipad-simulator\u003e\" \\\n  -Pmessaging.baseUrl=http://localhost:8080\n```\n\nIf you prefer Xcode, open `app/iosApp/iosApp.xcodeproj`, choose any installed iPad Simulator, and run the `iosApp` scheme.\n\n## Test and Verification\n\nCore verification commands:\n\n```bash\n./gradlew :shared:jvmTest\n./gradlew :api:test\n./gradlew :app:androidApp:assembleDebug\n./gradlew :app:desktopApp:compileKotlin\nswift test --package-path app/macosApp\nxcodebuild -project app/iosApp/iosApp.xcodeproj -scheme iosApp -showdestinations\nxcodebuild -project app/iosApp/iosApp.xcodeproj -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=\u003cinstalled-iphone-simulator\u003e' build\nxcodebuild -project app/iosApp/iosApp.xcodeproj -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=\u003cinstalled-ipad-simulator\u003e' build\n```\n\nNotes:\n\n- `:api:test` uses Testcontainers and requires Docker\n- iOS simulator names and OS versions must match the runtimes installed in your local Xcode\n- `:app:androidApp:assembleDebug` verifies the Android module without requiring a running emulator\n- `:app:androidApp:installDebug` requires an available emulator or connected device, but the default base URL is set up for the Android emulator\n\n## Common Local Issues\n\n- `Docker is not running`\n  - Start Docker Desktop or your Docker daemon first.\n- `Port 5432 already in use`\n  - Change the local PostgreSQL mapping or stop the conflicting service.\n- `Android build fails`\n  - Provide a valid Android SDK via `ANDROID_HOME` or `local.properties`.\n- `adb` or `emulator` command is not found\n  - Install the Android SDK command-line tools and add the SDK `platform-tools` and `emulator` directories to your `PATH`.\n- `No iOS simulator matches the destination`\n  - Run `xcodebuild -showdestinations` or `xcrun simctl list devices available` and replace the simulator name with one installed in your Xcode.\n- `simctl install` cannot find `KMPs.app`\n  - Re-run the `xcodebuild` step and resolve the app path again from `~/Library/Developer/Xcode/DerivedData`.\n- `API tests fail on container startup`\n  - Check Docker availability and container permissions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fretheviper%2Fkotlinmultiplatformsample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fretheviper%2Fkotlinmultiplatformsample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fretheviper%2Fkotlinmultiplatformsample/lists"}