/KMP-App-Template

Kotlin Multiplatform app template with shared UI

Primary LanguageKotlinApache License 2.0Apache-2.0

Kotlin Multiplatform app template

official project License

This is a basic Kotlin Multiplatform app template for Android and iOS. It includes shared business logic and data handling, and a shared UI implementation using Compose Multiplatform.

The template is also available with native UI written in Jetpack Compose and SwiftUI.

The amper branch showcases the same project configured with Amper.

Screenshots of the app

Technologies

The data displayed by the app is from The Metropolitan Museum of Art Collection API.

The app uses the following multiplatform dependencies in its implementation:

These are just some of the possible libraries to use for these tasks with Kotlin Multiplatform, and their usage here isn't a strong recommendation for these specific libraries over the available alternatives. You can find a wide variety of curated multiplatform libraries in the kmp-awesome repository.

Using Koin Annotations

KSP Setup

To setup Koin Annotations & KSP you need the following:

Gradle Toml file update:

Compose App Build Gradle update:

alias(libs.plugins.ksp)
api(libs.koin.annotations)

Extra KSP Configurations:

sourceSets.named("commonMain").configure {
    kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
}
dependencies {
    add("kspCommonMainMetadata", libs.koin.ksp.compiler)
    add("kspAndroid", libs.koin.ksp.compiler)
    add("kspIosX64", libs.koin.ksp.compiler)
    add("kspIosArm64", libs.koin.ksp.compiler)
    add("kspIosSimulatorArm64", libs.koin.ksp.compiler)
}
project.tasks.withType(KotlinCompilationTask::class.java).configureEach {
    if(name != "kspCommonMainKotlinMetadata") {
        dependsOn("kspCommonMainKotlinMetadata")
    }
}

Organize Modules & Components

All Koin configuration is available here: Koin.kt

Inside this configuration file:

  • All definitions are available as module classes, annotated with @Module
  • The @ComponentScan allows scanning annotated components for a given package (across Gradle modules)
  • Class components are tagged with @Single or @KoinViewModel
  • Some components are defined inside annotated Kotlin functions (Json & Http Client)

Sharing Multiplatform Native Components

The NativeModule class allow to share native components with expect/actual mechanism. The idea is to inject a native implementation of PlatformComponent class. Here is how it's organized:

In common Kotlin sourceSet:

@Module
expect class NativeModule
expect class PlatformComponent {
    fun sayHello() : String
}

In Android sourceSet:

@Module
@ComponentScan("com.jetbrains.kmpapp.native")
actual class NativeModule
@Single
actual class PlatformComponent(val context: Context){
    actual fun sayHello() : String = "I'm Android - $context"
}

In iOS sourceSet:

@Module
@ComponentScan("com.jetbrains.kmpapp.native")
actual class NativeModule
@Single
actual class PlatformComponent{
    actual fun sayHello() : String = "I'm iOS"
}

Injecting dynamic parameters with @InjectedParam

Let's create an Id generator class that will receive some a prefix, to generate ids:

@Factory
class IdGenerator(@InjectedParam private val prefix : String) {
    
    fun generate() : String = prefix + KoinPlatformTools.generateId()
}

We are using @InjectedParam to indicates that prefix property will be dynamically injected via a call with parametersOf.

Later in our code, lets call our generator:

val idGen = KoinPlatform.getKoin().get<IdGenerator> { parametersOf("_prefix_") }.generate()
println("Id => $idGen")

It should produces something like _prefix_d1a7ac22-0aee-4f3c-9d5c-41e6bfae5676

Customize Koin Configuration

To allow the use of Koin in multiplatform style, but allow some special configuration (like injecting Android context), we can allow this kind of startup function:

// config allow to extend Koin configuration from caller side
fun initKoin(config : KoinAppDeclaration ?= null) {
    startKoin {
        modules(
            AppModule().module,
        )
        // call for any extra configuration
        config?.invoke(this)
    }
}

On Android startup:

class MuseumApp : Application() {
    override fun onCreate() {
        super.onCreate()
        initKoin {
            androidContext(this@MuseumApp)
        }
    }
}