Extending the KMP `shared` Module
Relevant source files
The following files were used as context for generating this wiki page:
The shared module is a core component of the Vocabri project, designed using Kotlin Multiplatform (KMP) to facilitate code sharing between different target platforms. Its primary purpose is to contain business logic, data models, and platform-agnostic functionalities that can be compiled and run on both Android and a future iOS app. This approach minimizes code duplication and ensures consistency across platforms.
This module is already prepared for future iOS support, laying the groundwork for a cross-platform application. It leverages the expect/actual pattern to handle platform-specific APIs while maintaining a common codebase for shared logic. Extending this module is the standard way to add new features that should be available on all supported platforms.
Sources: README.md, shared/build.gradle.kts
Module Architecture and Gradle Configuration
The structure and configuration of the shared module are defined in its build.gradle.kts file. It applies the kotlin-multiplatform and com.android.library plugins to enable KMP and Android library builds.
Sources: shared/build.gradle.kts:19-20
The module is organized into source sets, which is a key concept in KMP for separating common and platform-specific code.
commonMain: Contains code that is shared across all platforms. This includesexpectdeclarations and platform-independent logic.androidMain: Contains the Android-specificactualimplementations of theexpectdeclarations fromcommonMain. It can also include code that uses Android-specific APIs.iosMain: Contains the iOS-specificactualimplementations and depends oncommonMain.
Sources: shared/build.gradle.kts:23-45
The following diagram illustrates the source set structure and their dependencies.
Sources: shared/build.gradle.kts:23-45
Core Dependencies
The shared module relies on several key libraries for its functionality, defined in shared/build.gradle.kts and versioned in gradle/libs.versions.toml.
| Library | Purpose | Source Set |
|---|---|---|
kotlinx-coroutines | Provides support for asynchronous programming. | commonMain |
ktor-client-core | Core library for making HTTP requests. | commonMain |
ktor-client-okhttp | Ktor client engine for Android. | androidMain |
ktor-client-darwin | Ktor client engine for iOS. | iosMain |
kermit-log | A multiplatform logging utility. | commonMain |
Sources: shared/build.gradle.kts:47-64, gradle/libs.versions.toml:21, 26, 30
The expect/actual Pattern
KMP uses the expect/actual mechanism to access platform-specific APIs from common code. This allows shared logic in commonMain to call code that has different implementations on Android and iOS.
expect Declaration
In the commonMain source set, an expect class, function, or property is declared. This defines an API that the common code can use without providing an implementation. The Platform class is a primary example of this pattern in the module. The Greeting class in commonMain uses this expect class to get the platform name.
// shared/src/commonMain/kotlin/com/vocabri/shared/Greeting.kt:4-8
class Greeting {
private val platform: Platform = Platform()
fun greet(): String {
return "Hello, ${platform.name}!"
}
}
Sources: shared/src/commonMain/kotlin/com/vocabri/shared/Greeting.kt:4-8
actual Implementation
For each target platform, an actual implementation must be provided for every expect declaration.
Android Implementation (androidMain)
The Android implementation provides the platform name as "Android" followed by the SDK version.
// shared/src/androidMain/kotlin/com/vocabri/shared/Platform.android.kt:6-8
actual class Platform {
actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
Sources: shared/src/androidMain/kotlin/com/vocabri/shared/Platform.android.kt:6-8
iOS Implementation (iosMain)
The iOS implementation uses UIDevice to provide the device's system name and version.
// shared/src/iosMain/kotlin/com/vocabri/shared/Platform.ios.kt:7-10
import platform.UIKit.UIDevice
actual class Platform {
actual val name: String =
UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
Sources: shared/src/iosMain/kotlin/com/vocabri/shared/Platform.ios.kt:7-10
This relationship can be visualized with the following class diagram.
How to Add a New Shared Feature
To extend the shared module with new functionality, follow these steps. This process ensures that the new feature is available on all target platforms while allowing for platform-specific implementations where necessary.
The following flowchart outlines the development workflow for adding a new shared component.
Step-by-step Guide
-
Define the API in
commonMain:- If your feature requires platform-specific code (e.g., accessing a device sensor), create an
expectclass, function, or property in a new Kotlin file withinshared/src/commonMain/kotlin. - If the feature is pure logic and has no platform dependencies, you can implement it directly in
commonMainwithout usingexpect/actual.
- If your feature requires platform-specific code (e.g., accessing a device sensor), create an
-
Provide Platform-Specific Implementations:
- In
shared/src/androidMain/kotlin, create a corresponding file and provide theactualimplementation for the Android target. You can use any Android-specific libraries here. - In
shared/src/iosMain/kotlin, do the same for the iOS target, using iOS-specific APIs (e.g., from Foundation, UIKit).
- In
-
Add Dependencies (if needed):
- If your new feature requires a new library, add it to the appropriate source set's dependencies block in
shared/build.gradle.kts. - For a common library, add it to
commonMain. - For a platform-specific library, add it to
androidMainoriosMain. - Ensure the library version is managed in
gradle/libs.versions.toml.
- If your new feature requires a new library, add it to the appropriate source set's dependencies block in
-
Utilize the New Feature:
- You can now call your new function or use your new class from any other code within the
commonMainsource set, just as theGreetingclass usesPlatform.
- You can now call your new function or use your new class from any other code within the
Sources: shared/build.gradle.kts, shared/src/commonMain/kotlin/com/vocabri/shared/Greeting.kt, shared/src/androidMain/kotlin/com/vocabri/shared/Platform.android.kt, shared/src/iosMain/kotlin/com/vocabri/shared/Platform.ios.kt
Conclusion
The shared module is fundamental to Vocabri's multiplatform strategy. It provides a robust framework for writing common code once and deploying it on both Android and iOS. By using the expect/actual pattern and a well-defined Gradle structure, developers can efficiently extend the application's feature set across all platforms. Understanding this architecture is key to contributing new, scalable features to the project.
Sources: README.md, shared/build.gradle.kts