A production-ready Kotlin Multiplatform library starter template with Compose Multiplatform UI for Android, iOS, and JVM platforms.
KMPLibraryStarter provides a solid foundation for building cross-platform libraries with shared UI components. It follows Clean Architecture principles and includes modern tooling for code quality, testing, and dependency injection.
- Kotlin Multiplatform - Share code across Android, iOS, and JVM
- Compose Multiplatform - Build shared UI with Jetpack Compose
- Clean Architecture - Clear separation of data, domain, and UI/presentation layers
- Convention Plugins - Consistent build configuration across modules
- Dependency Injection - Compile-time safe DI with kotlin-inject
- Database - Room 3 with bundled SQLite for multiplatform persistence
- Networking - Ktor client with platform-specific engines
- Testing - KoTest framework with MockK
- Code Quality - Detekt, ktlint, and aggregated Jacoco coverage
- Automation - GitHub Actions check workflow and Dependabot updates
- Agent Guidance - Root-level
AGENTS.mdandCLAUDE.mdfor AI-assisted maintenance
| Platform | Min Version | Notes |
|---|---|---|
| Android | API 26 | Jetpack Compose |
| iOS | arm64, simulatorArm64 | Compose Multiplatform |
| JVM | Java 11 bytecode target | Build with JDK 17 |
KMPLibraryStarter/
├── build-logic/
│ └── convention/ # Gradle convention plugins
├── core/
│ ├── common/ # Shared utilities, dispatchers, extensions
│ ├── data/ # Repositories, database, network clients
│ ├── datastore/ # User preferences (DataStore)
│ ├── designsystem/ # Theme, colors, typography, shapes
│ ├── domain/ # Use cases, repository interfaces
│ ├── testing/ # Test utilities and doubles
│ └── ui/ # Compose UI components
├── config/
│ └── detekt/ # Static analysis configuration
└── gradle/
└── libs.versions.toml # Dependency version catalog
┌─────────────┐
│ core:ui │
└──────┬──────┘
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│designsystem │ │ domain │ │ common │
└──────────────┘ └──────┬──────┘ └─────────────┘
│ ▲
▼ │
┌─────────────┐ │
│ data │────────┘
└──────┬──────┘
│
▼
┌─────────────┐
│ datastore │
└─────────────┘
- JDK 17
- Latest stable Android Studio or IntelliJ IDEA
- Latest stable Xcode (required for iOS framework builds)
- The build runs on JDK 17, while Android and JVM artifacts stay on Java 11 bytecode for broader consumer compatibility
git clone https://github.com/Sermilion/KMPLibraryStarter.git
cd KMPLibraryStarter
# Build all modules
./gradlew build
# Run tests
./gradlew check
# Format code
./gradlew spotlessApply- Read
AGENTS.mdbefore making structural, build, or release-related changes. - Use
./gradlew checkas the default validation gate for local changes and CI. - Update shared dependency versions through
gradle/libs.versions.tomlinstead of hardcoding them in module scripts.
- Open the project in Android Studio or IntelliJ IDEA
- Wait for Gradle sync to complete
- For iOS development, open
iosApp/in Xcode
To build the iOS framework for your KMP library:
# Build currently configured iOS targets
./gradlew :core:ui:linkIosArm64 # Physical device
./gradlew :core:ui:linkIosSimulatorArm64 # Simulator (Apple Silicon)Artifacts are generated under core/ui/build/bin/ios*/
Option 1: CocoaPods (Recommended for published libraries)
Create a podspec file for your library:
Pod::Spec.new do |spec|
spec.name = 'KmpLibraryStarter'
spec.version = '1.0.0'
spec.homepage = 'https://github.com/Sermilion/KMPLibraryStarter'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'KMP Library Starter'
spec.vendored_frameworks = 'build/cocoapods/framework/KmpLibraryStarter.framework'
spec.ios.deployment_target = '14.0'
endIn your iOS project's Podfile:
target 'YourApp' do
use_frameworks!
pod 'KmpLibraryStarter', :path => '../path/to/KMPLibraryStarter'
endOption 2: Swift Package Manager (SPM)
Create a Package.swift for your library (requires XCFramework):
- Build XCFramework for all targets:
./gradlew assembleXCFramework- Add to SPM:
// Package.swift
let package = Package(
name: "KmpLibraryStarter",
products: [
.library(name: "KmpLibraryStarter", targets: ["KmpLibraryStarter"])
],
targets: [
.binaryTarget(
name: "KmpLibraryStarter",
path: "./KmpLibraryStarter.xcframework"
)
]
)Option 3: Direct Framework Integration (For local development)
- Build the framework as shown above
- In Xcode, drag the
.frameworkfile to your project - Add to "Frameworks, Libraries, and Embedded Content"
- Set "Embed" to "Embed & Sign"
- MockK is not supported on iOS (Kotlin Native limitation)
- Use Mokkery for multiplatform mocking, or create manual test doubles
- KoTest works on all platforms including iOS
The project uses convention plugins for consistent build configuration:
| Plugin | Purpose |
|---|---|
kmp.library |
Base KMP library setup with Android-KMP, iOS, and JVM targets |
kmp.compose |
KMP library with Compose Multiplatform |
kmp.jacoco |
Aggregated code coverage reporting for JVM and Android host tests |
kmp.lint |
Android lint configuration |
kmp.detekt |
Static analysis with Detekt |
// build.gradle.kts
plugins {
alias(libs.plugins.kmp.library) // For non-UI modules
alias(libs.plugins.kmp.compose) // For Compose modules
alias(libs.plugins.kmp.jacoco) // For coverage
}Shared utilities available to all modules:
// Extension functions
fun Int?.orZero(): Int
fun <T> T?.require(): T
fun <T> Flow<T?>?.orEmpty(): Flow<T?>
// Coroutine dispatchers with testable interface
interface DispatcherProviderContract {
fun io(): CoroutineDispatcher
fun main(): CoroutineDispatcher
fun default(): CoroutineDispatcher
}Material 3 design system with custom theming:
@Composable
fun MyScreen() {
KmpMaterialTheme {
val colors = KmpTheme.colors // M3 ColorScheme
val kmpColors = KmpTheme.kmpColors // Custom colors
val typography = KmpTheme.typography
val shapes = KmpTheme.shapes
// Your UI here
}
}Business logic layer with repository interfaces and use cases:
interface UserRepository {
fun getUsers(): Flow<List<User>>
suspend fun getUserById(id: String): User?
}
class GetUserUseCase @Inject constructor(
private val repository: UserRepository,
) {
operator fun invoke(id: String): Flow<User?> =
repository.getUsers().map { users -> users.find { it.id == id } }
}Data layer with Room 3 database and Ktor networking:
@Entity(tableName = "users")
data class UserDataModel(
@PrimaryKey val id: String,
val name: String,
val email: String?,
val createdAt: Long,
)
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY createdAt DESC")
fun observeUsers(): Flow<List<UserDataModel>>
@Query("SELECT * FROM users WHERE id = :id LIMIT 1")
suspend fun getById(id: String): UserDataModel?
@Upsert
suspend fun upsert(user: UserDataModel)
}User preferences with AndroidX DataStore:
@Serializable
data class UserPreferences(
val isLoggedIn: Boolean = false,
val userId: String? = null,
)Test utilities and doubles:
class TestDispatcherProvider(
private val testDispatcher: TestDispatcher = StandardTestDispatcher()
) : DispatcherProviderContract {
override fun io() = testDispatcher
override fun main() = testDispatcher
override fun default() = testDispatcher
}Uses kotlin-inject for compile-time safe DI:
@Inject
class UserRepositoryImpl(
private val database: UserDatabase,
private val dispatchers: DispatcherProviderContract,
) : UserRepository {
// Implementation
}The project uses KoTest with FunSpec style:
class UserRepositoryTest : FunSpec({
val testDispatcher = TestDispatcherProvider()
test("getUsers returns empty list when database is empty") {
// Arrange
val repository = createRepository()
// Act
val result = repository.getUsers().first()
// Assert
result.shouldBeEmpty()
}
})Run tests:
# All tests
./gradlew check
# Specific module
./gradlew :core:data:check
# JVM tests only
./gradlew jvmTest
# Android unit tests
./gradlew testDebugUnitTest./gradlew detektConfiguration: config/detekt/detekt.yml
# Check formatting
./gradlew spotlessCheck
# Apply formatting
./gradlew spotlessApply./gradlew jacocoTestReportReports: build/reports/jacoco/
The report aggregates the enabled JVM and Android host test tasks for each module that applies kmp.jacoco.
- Create the module directory:
core/
└── newmodule/
├── build.gradle.kts
└── src/
├── commonMain/kotlin/
├── androidMain/kotlin/
├── iosMain/kotlin/
└── jvmMain/kotlin/
- Add to
settings.gradle.kts:
include(":core:newmodule")- Configure
build.gradle.kts:
plugins {
alias(libs.plugins.kmp.library)
}
kotlin {
android {
namespace = "com.sermilion.kmpstarter.core.newmodule"
}
sourceSets {
commonMain.dependencies {
implementation(projects.core.common)
}
}
}To change the package name from com.sermilion.kmpstarter:
- Update
namespacein all modulebuild.gradle.ktsfiles - Rename package directories in
src/*/kotlin/ - Update imports in all Kotlin files
- Update Room entities, DAO, and builder packages in
core/data/src/*/kotlin/
Modify core/designsystem/src/commonMain/kotlin/.../theme/Color.kt:
val Green500 = Color(0xFFA2C617) // Primary color
val Green600 = Color(0xFF92B215) // Primary variant
// Add your brand colorsModify core/designsystem/src/commonMain/kotlin/.../theme/Typography.kt:
val KmpTypography = Typography(
displayLarge = TextStyle(
fontFamily = FontFamily.Default, // Replace with custom font
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
),
// ...
)| Category | Library |
|---|---|
| Language | Kotlin 2.3.10 |
| UI | Compose Multiplatform 1.10.2 |
| Async | Kotlin Coroutines 1.10.2 |
| DI | kotlin-inject |
| Database | Room 3.0.0-alpha01 |
| Network | Ktor 3.3.2 |
| Preferences | AndroidX DataStore |
| Images | Coil 3 |
| Paging | AndroidX Paging 3 |
| Testing | KoTest 5.9.1, MockK |
| Static Analysis | Detekt 1.23.8 |
| Formatting | Spotless + ktlint |
MIT License
Copyright (c) 2024
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Contributions are welcome! Please feel free to submit a Pull Request.
Before making substantive changes, read AGENTS.md and docs/ARCHITECTURE.md, then the focused
documents under docs/architecture/, for repository-specific build, architecture, naming, and
testing conventions.