Skip to content

Repository files navigation

Wangdu

Kotlin Compose Multiplatform Ktor Koin Platforms License

A real time collaborative whiteboard built with Kotlin Multiplatform and Compose Multiplatform. Multiple clients draw on a shared canvas, see each other's live cursors, and stay in sync over WebSockets with server side persistence.

Special Mentions

  • Template Generated via Catylst KMP Starter (Android Studio Plugin)

Demo

Screenshot 2026-07-02 at 1 46 01 AM

Features

  • Real time collaborative drawing synced across all connected clients
  • Live cursor presence so you can see where others are pointing
  • Broadcasting of stroke events to every client over WebSockets
  • Server side persistence so boards survive restarts and late joiners
  • Configurable display name on join
  • Shared toolbar for drawing controls

Tech Stack

  • Language: Kotlin 2.3.21
  • UI: Compose Multiplatform 1.11.0-rc01, Material3 1.11.0-alpha06
  • Networking: Ktor 3.4.3 (client and server, WebSockets)
  • Dependency Injection: Koin 4.1.1
  • Navigation: Navigation3 1.1.1
  • Serialization: kotlinx.serialization 1.9.0
  • Persistence: Exposed 0.56.0 with SQLite JDBC 3.47.1.0
  • Concurrency: kotlinx.coroutines 1.10.2
  • Build: Gradle with AGP 9.1.1, KSP 2.3.7

Platforms

  • Android
  • iOS
  • Desktop (JVM)

Modules

  • composeApp — shared Compose Multiplatform UI and client logic
  • androidApp — Android application entry point
  • server — Ktor WebSocket server with persistence
  • shared — models and code shared between client and server

Getting Started

Prerequisites

  • JDK 17 or newer
  • Android Studio or IntelliJ IDEA (latest stable)
  • Xcode (only required for the iOS target)

Setup

  1. Clone the repository
  2. Open the project in Android Studio or IntelliJ IDEA
  3. Sync Gradle

Run the server

./gradlew :server:run

Run the Android app

./gradlew :androidApp:assembleDebug

Run the Desktop app

./gradlew :composeApp:run

Run the Web app

./gradlew :composeApp:wasmJsBrowserDevelopmentRun

Run the iOS app

Open the iOS project in Xcode and run it on a simulator or device.

Run the Web app (Kotlin/Wasm)

./gradlew :composeApp:wasmJsBrowserDevelopmentRun

Production Deployment

The app runs in production with the server on Render and the web client on Vercel. All clients (Web, Desktop, iOS, Android) point at the deployed server over wss:// (see the platform entry points, e.g. composeApp/src/wasmJsMain/kotlin/io/jadu/wangdu/main.kt).

Server → Render (Docker)

The Dockerfile at the repo root builds and runs the Ktor server as a Docker web service.

  • Language: Docker
  • Root Directory: leave blank (the build context must be the repo root, since the server depends on :shared and the Gradle wrapper)
  • Port: the app reads Render's injected PORT env var automatically (Application.kt); no configuration needed
  • Persistence (optional): attach a Render Disk mounted at /data to keep the SQLite database (DB_PATH defaults to /data/whiteboard.db). Without a disk, board data resets on each deploy since the container filesystem is ephemeral.

A trimmed deploy/settings.gradle.kts is used inside the container so only :server and :shared are built (the app modules are skipped). Render redeploys automatically on every push to main.

Web client → Vercel (GitHub Actions)

.github/workflows/deploy-web.yml builds the Kotlin/Wasm distribution and deploys it to Vercel on every push to main.

Required GitHub repository secrets:

Secret Where to find it
VERCEL_TOKEN Vercel → Settings → Tokens → Create Token
VERCEL_ORG_ID Your Vercel Team ID (Settings → General), or orgId in .vercel/project.json after npx vercel link
VERCEL_PROJECT_ID projectId in .vercel/project.json after npx vercel link

To deploy the web app manually instead:

./gradlew :composeApp:wasmJsBrowserDistribution
cd composeApp/build/dist/wasmJs/productionExecutable
npx vercel --prod

Before Going to Production

This project is built for learning. The choices below keep it simple to run on a single machine, but they are not safe or scalable for real users. Treat this as a map of what to change before shipping.

  1. Replace SQLite with Postgres. SQLite is a single file on disk. It locks on concurrent writes, so many users drawing at once contend for the same file. Postgres handles concurrent writes safely and provides proper backups and replication.

  2. Switch from ws:// to wss://. Plain WebSocket traffic is unencrypted, so anyone on the network path can read or tamper with it. Production must use wss:// (WebSocket Secure), which needs an SSL certificate on the server, usually terminated by a reverse proxy such as Nginx or Caddy.

  3. Add authentication. Any client can currently connect and claim any display name. Users should sign in and the server should verify their identity before accepting events, so names and actions can be trusted.

  4. Add room support. Every connected user shares one global board today. A real app gives each board its own room with a separate WebSocket route, session registry, and database partition, so boards stay isolated.

  5. Add rate limiting. A malicious or buggy client could send thousands of stroke events per second and overload the server. Limiting the event rate per session protects the server and keeps one client from degrading the experience for everyone.

  6. Handle server restarts gracefully. If the server restarts, clients currently lose their connection with no explanation. Clients should detect the disconnect and automatically reconnect with exponential backoff so a restart is a brief blip rather than a dead app.

  7. Add stroke count limits. The database currently stores strokes forever, so a board grows without bound. Production boards should cap the number of strokes or expire old ones by age to keep storage and load times under control.

Future Improvements

Beyond production hardening, here are ideas to build on top of this project:

  1. Undo and redo. Track a per user history of stroke operations so a client can revert its own actions without affecting others.

  2. Multiple boards and a lobby. Add a home screen listing available boards, creating new ones, and joining by code, backed by the room support described above.

  3. Richer drawing tools. Add shapes, text, arrows, an eraser, fill, and adjustable stroke width and opacity beyond freehand drawing.

  4. Export and share. Let users export a board as an image or vector file, or share a read only snapshot link.

  5. Infinite and zoomable canvas. Support panning and zooming on a large virtual canvas with viewport based rendering for performance.

  6. Presence and chat. Show an avatar list of who is online and add a lightweight text chat alongside the board.

  7. Conflict free sync with CRDTs. Move from broadcast based sync to a CRDT model so offline edits merge cleanly when clients reconnect.

  8. Media on the canvas. Allow dropping images or sticky notes onto the board, uploaded to object storage and referenced by URL.

  9. Web target. Extend the Compose Multiplatform app to run in the browser via Kotlin/Wasm so the whiteboard works without an install.

  10. Playback and versioning. Persist an event log so a board can be replayed over time or restored to an earlier state.

Contributing

Contributions are welcome. Please read the Contributing Guidelines to get started, and note that this project follows a Code of Conduct.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Build with Love ❤️ by Jadu

About

A real time collaborative whiteboard

Topics

Resources

Code of conduct

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages