A base repo for design-forward Android apps: Kotlin + Compose Multiplatform (Android target today, iOS-ready tomorrow) + Material 3 + physics-based motion.
Every "delight" technique ships as a working demo screen — clone, run, steal.
| Concern | Choice |
|---|---|
| Language | Kotlin (multiplatform) |
| UI | Compose Multiplatform 1.9.0 — all UI lives in commonMain |
| Design system | Material 3 + Material You dynamic color, extra-round expressive shapes |
| State | Coroutines + Flow, collectAsStateWithLifecycle, ViewModel |
| DI | (add Hilt or Koin when the app grows) |
| Navigation | Tiny state-driven Navigator routed through AnimatedContent |
| Build | AGP 8.10 / Gradle 8.14 / Kotlin 2.2 |
The composeApp module is a standard KMP module — it just only declares
androidTarget(). When iOS lands on the roadmap, add the iOS targets in
composeApp/build.gradle.kts (marked with a comment), a SwiftUI shell, and
done — every screen in commonMain is already multiplatform-safe. Platform
differences are isolated behind expect/actual in platform/ (see
Platform.android.kt for dynamic color, back handling, and AGSL shaders).
| Technique | Demo | Source |
|---|---|---|
Spring physics (spring(), Animatable, interruptible motion) |
Drag-the-ball playground with damping/stiffness sliders | ui/SpringsScreen.kt |
Shared element transitions (SharedTransitionLayout + sharedElement) |
Grid → detail hero animation | ui/SharedElementsScreen.kt, root layout in App.kt |
AnimatedVisibility / AnimatedContent / animateContentSize |
Toggle card, counter, expanding card | ui/MotionScreen.kt |
| Animated nav transitions | Screen-to-screen slide/fade via AnimatedContent |
App.kt |
AGSL RuntimeShader + graphicsLayer |
Animated shader backdrop + 3D-tilting card | ui/GraphicsScreen.kt, platform/Platform.android.kt |
Haptics (LocalHapticFeedback) |
Tick vs. thud vs. snap, press-and-hold squash | ui/HapticsScreen.kt |
| Material 3 Expressive-adjacent components | Segmented buttons, progress, expressive shapes | ui/ExpressiveScreen.kt, theme/Theme.kt |
| Material You dynamic color | Automatic on Android 12+ | theme/Theme.kt |
| Staggered entrances | Home list items cascade in | ui/HomeScreen.kt |
| Predictive back | enableOnBackInvokedCallback in the manifest |
AndroidManifest.xml |
| Edge-to-edge | enableEdgeToEdge() (mandatory on targetSdk 35 anyway) |
MainActivity.kt |
composeApp/
src/
commonMain/kotlin/dev/delight/app/
App.kt # theme + SharedTransitionLayout + animated nav host
navigation/Navigator.kt # minimal state-driven back stack
theme/Theme.kt # M3 theme, dynamic color hook, expressive shapes
platform/Platform.kt # expect declarations (dynamic color, back, shader)
model/ # ViewModel + demo data
ui/ # one file per demo screen
androidMain/
AndroidManifest.xml # predictive back, edge-to-edge
kotlin/.../MainActivity.kt
kotlin/.../platform/Platform.android.kt # actual implementations
- In
composeApp/build.gradle.kts: addiosX64(); iosArm64(); iosSimulatorArm64()next toandroidTarget(). - Create
iosMainactuals for the threeexpectdeclarations inplatform/Platform.kt(all have sensible fallbacks). - Add an
iosAppXcode project that callsMainViewController(). - For production navigation, migrate
Navigatorto Navigation 3 (androidx.navigation3) or CMPnavigation-composewith serialized keys — both give process-death restoration and predictive back previews.
releasebuild already enables R8 + resource shrinking.- Add Baseline Profiles
(
androidx.baselineprofile) and Macrobenchmark before launch. - Use Compose compiler metrics (
composeCompiler { reportsDestination }) to audit stability/skippability. - Coil 3 for images; Rive or Lottie for hero animations — add when needed.
./gradlew :composeApp:assembleDebugRequires JDK 17+ and Android SDK 35 (local.properties → sdk.dir=..., or
ANDROID_HOME).