Skip to content

Commit

Permalink
add wasm support to kmp-viewmodel-compose (#143)
Browse files Browse the repository at this point in the history
* compose wasm

* koject: update
mori-atsushi/koject#312

* compose wasm

* compose wasm

* compose: use `browser()` instead of NodeJS.

* compose: use `browser()` instead of NodeJS.

* changelog

* docs for koject

* docs for koject

* Update CHANGELOG.md

- `kmp-viewmodel-koin` and `kmp-viewmodel-koin-compose`: blocked by InsertKoinIO/koin#1634
  • Loading branch information
hoc081098 authored Feb 29, 2024
1 parent ca7d178 commit 517baab
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 30 deletions.
26 changes: 26 additions & 0 deletions .idea/appInsightsSettings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,47 @@

## [Unreleased] - TBD

### `kmp-viewmodel-compose` artifact

- [JetBrains Compose Multiplatform `1.6.0`](https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.6.0).
- **New**: Add support for Kotlin/Wasm (`wasmJs` target) 🎉.

### Added `kmp-viewmodel-koject` and `kmp-viewmodel-koject-compose` artifacts

- For more information check out the [docs/0.x/viewmodel-koject-compose](https://hoc081098.github.io/kmp-viewmodel/docs/0.x/viewmodel-koject-compose/)

- The [Koject](https://mori-atsushi.github.io/koject/) dependency are used in `kmp-viewmodel-koject-compose`: `com.moriatsushi.koject:koject-core:1.3.0`.

- **New** The `kmp-viewmodel-koject` artifact provides the integration of `kmp-viewmodel`, `kmp-viewmodel-compose` and `Koject`,
helps us to retrieve `ViewModel` from the Koject DI container without manually dependency injection.

```kotlin
@Provides
@Singleton
class MyRepository

@Provides
@ViewModelComponent // <-- To inject SavedStateHandle
class MyViewModel(
val myRepository: MyRepository,
val savedStateHandle: SavedStateHandle,
) : ViewModel() {
// ...
}

@Composable
fun MyScreen(
viewModel: MyViewModel = kojectKmpViewModel(),
) {
// ...
}
```

### Example, docs and tests

- Add [Compose Multiplatform Koject sample](https://github.com/hoc081098/kmp-viewmodel/tree/master/standalone-sample/kmpviewmodel_compose_koject_sample)
which shares `ViewModel`s and integrates with `Navigation` in Compose Multiplatform. It uses `Koject` for DI.

## [0.7.0] - Feb 17, 2024

### Update dependencies
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ For more information check out the [docs][4].

For more information check out the [docs][5].

### 6. `kmp-viewmodel` and Navigation for JetBrains Compose Multiplatform.
### 6. `kmp-viewmodel-koject-compose` library

For more information check out the [docs][6].

### 7. `kmp-viewmodel` and Navigation for JetBrains Compose Multiplatform.

For more information check out https://github.com/hoc081098/solivagant library.

Expand Down Expand Up @@ -145,3 +149,4 @@ Copyright (c) 2023-2024 Petrus Nguyễn Thái Học
[3]: https://hoc081098.github.io/kmp-viewmodel/docs/0.x/swift-interop/
[4]: https://hoc081098.github.io/kmp-viewmodel/docs/0.x/viewmodel-compose/
[5]: https://hoc081098.github.io/kmp-viewmodel/docs/0.x/viewmodel-koin-compose/
[6]: https://hoc081098.github.io/kmp-viewmodel/docs/0.x/viewmodel-koject-compose/
142 changes: 142 additions & 0 deletions docs/viewmodel-koject-compose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# ViewModel Compose Multiplatform and Koject

[![maven-central](https://img.shields.io/maven-central/v/io.github.hoc081098/kmp-viewmodel-koject-compose)](https://search.maven.org/search?q=g:io.github.hoc081098%20kmp-viewmodel-koject-compose)

Koject integration with **Kotlin Multiplatform ViewModel** and **Jetpack Compose Multiplatform**.

**Koject** is a DI Container Library for **Kolin Multiplatform** using KSP.
For more information check out the [Koject documentation](https://mori-atsushi.github.io/koject/).

The `kmp-viewmodel-koject-compose` artifact provides the integration of `kmp-viewmodel-compose`
and `Koject`,
helps us to retrieve `ViewModel` in `@Composable` functions from the Koject DI container
without manually dependency injection.

## 1. Add dependency

- Add `mavenCentral()` to `repositories` list in `build.gradle.kts`/`settings.gradle.kts`.

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
[...]
repositories {
mavenCentral()
[...]
}
}
```

- Add dependency to `build.gradle.kts` of your shared module.

```kotlin
// build.gradle.kts
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.hoc081098:kmp-viewmodel-koject-compose:0.7.0")

// NOTE: You can add `koject-core` dependency to your project to specify the version of Koject.
// For more information check out the [Koject Setup documentation](https://mori-atsushi.github.io/koject/docs/setup#multiplatform).
implementation("com.moriatsushi.koject:koject-core:${kojectVersion}")
}
}
}
}

dependencies {
val processor = "com.moriatsushi.koject:koject-processor-app:${kojectVersion}"

fun String.capitalizeUS() = replaceFirstChar {
if (it.isLowerCase()) {
it.titlecase(Locale.US)
} else {
it.toString()
}
}

kotlin
.targets
.names
.map { it.capitalizeUS() }
.forEach { target ->
val targetConfigSuffix = if (target == "Metadata") "CommonMainMetadata" else target
add("ksp$targetConfigSuffix", processor)
}
}
```

<details>
<summary>Snapshots of the development version are available in Sonatype's snapshots repository.</summary>
<p>

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/")
[...]
}
}

// build.gradle.kts
dependencies {
api("io.github.hoc081098:kmp-viewmodel-koject-compose:0.7.1-SNAPSHOT")
}
```

</p>
</details>

> [!NOTE]
> Make sure to setup KSP in your project.
> For more information check out
> the [Koject Setup documentation](https://mori-atsushi.github.io/koject/docs/setup#multiplatform).
## 2. Add `@Provides` and `@ViewModelComponent` to `ViewModel`

```kotlin
import com.hoc081098.kmp.viewmodel.ViewModel
import com.hoc081098.kmp.viewmodel.SavedStateHandle
import com.hoc081098.kmp.viewmodel.koject.ViewModelComponent
import com.moriatsushi.koject.Provides
import com.moriatsushi.koject.Singleton

@Provides
@Singleton
class MyRepository

@Provides
@ViewModelComponent // <-- To inject SavedStateHandle
class MyViewModel(
val myRepository: MyRepository,
val savedStateHandle: SavedStateHandle,
) : ViewModel() {
// ...
}
```

> [!NOTE]
> Make sure to start Koject in your project, for example:
>
> ```kotlin
> Koject.start()
> ```
> For more information check out
> the [Start Koject documentation](https://mori-atsushi.github.io/koject/docs/core/basic).
## 3. Retrieve `ViewModel` in `@Composable`s function via `kojectKmpViewModel` function
```kotlin
import com.hoc081098.kmp.viewmodel.koject.compose.kojectKmpViewModel
@Composable
fun MyScreen(
viewModel: MyViewModel = kojectKmpViewModel(),
) {
// ...
}
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ nav:
- 'Safe SavedStateHandle': viewmodel-savedstate-safe.md
- 'ViewModel Compose': viewmodel-compose.md
- 'ViewModel Koin Compose': viewmodel-koin-compose.md
- 'ViewModel Koject Compose': viewmodel-koject-compose.md
- 'ViewModel and Compose Navigation': viewmodel-compose-navigation.md
- 'Swift Interop': swift-interop.md
- 'Change Log': changelog.md
Expand Down
39 changes: 32 additions & 7 deletions viewmodel-compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@file:Suppress("ClassName")

import java.net.URL
import org.jetbrains.kotlin.gradle.dsl.JsModuleKind
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

@Suppress("DSL_SCOPE_VIOLATION")
Expand Down Expand Up @@ -48,14 +49,21 @@ kotlin {
}

js(IR) {
compilations.all {
kotlinOptions {
sourceMap = true
moduleKind = "commonjs"
moduleName = property("POM_ARTIFACT_ID")!!.toString()
compilations.configureEach {
compilerOptions.configure {
sourceMap.set(true)
moduleKind.set(JsModuleKind.MODULE_COMMONJS)
}
}
browser()
nodejs()
}
@OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class)
wasmJs {
// Module name should be different from the one from JS
// otherwise IC tasks that start clashing different modules with the same module name
moduleName = property("POM_ARTIFACT_ID")!!.toString() + "Wasm"
browser()
}

iosArm64()
Expand Down Expand Up @@ -140,16 +148,33 @@ kotlin {
}
}

jsMain {
val jsAndWasmMain by creating {
dependsOn(nonAndroidMain)
}
jsTest {
val jsAndWasmTest by creating {
dependsOn(nonAndroidTest)
}

jsMain {
dependsOn(jsAndWasmMain)
}
jsTest {
dependsOn(jsAndWasmTest)
dependencies {
implementation(kotlin("test-js"))
}
}

val wasmJsMain by getting {
dependsOn(jsAndWasmMain)
}
val wasmJsTest by getting {
dependsOn(jsAndWasmTest)
dependencies {
implementation(kotlin("test-wasm-js"))
}
}

nativeMain {
dependsOn(nonAndroidMain)
dependsOn(nonJsAndNonAndroidMain)
Expand Down
36 changes: 18 additions & 18 deletions viewmodel-koject/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@file:Suppress("ClassName")

import java.net.URL
import java.util.Locale
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

@Suppress("DSL_SCOPE_VIOLATION")
Expand Down Expand Up @@ -180,26 +181,25 @@ tasks.withType<org.jetbrains.dokka.gradle.DokkaTask>().configureEach {
}

dependencies {
// Add it according to your targets.
val processor = libs.koject.processor.lib
add("kspAndroid", processor)
add("kspJvm", processor)
add("kspJs", processor)
add("kspIosX64", processor)
add("kspIosArm64", processor)
add("kspIosSimulatorArm64", processor)
// add("kspTvosX64", processor)
// add("kspTvosSimulatorArm64", processor)
// add("kspTvosArm64", processor)
// add("kspWatchosArm32", processor)
add("kspWatchosArm64", processor)
// add("kspWatchosX64", processor)
// add("kspWatchosSimulatorArm64", processor)
add("kspMacosX64", processor)
add("kspMacosArm64", processor)
fun String.capitalizeUS() = replaceFirstChar {
if (it.isLowerCase()) {
it.titlecase(Locale.US)
} else {
it.toString()
}
}

kotlin
.targets
.names
.map { it.capitalizeUS() }
.forEach { target ->
val targetConfigSuffix = if (target == "Metadata") "CommonMainMetadata" else target
add("ksp$targetConfigSuffix", libs.koject.processor.lib)
}
}

ksp {
arg("moduleName", project.name)
arg("moduleName", property("POM_ARTIFACT_ID")!!.toString())
arg("measureDuration", "true")
}
4 changes: 2 additions & 2 deletions viewmodel-savedstate/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ kotlin {
}
}
js(IR) {
moduleName = project.name
moduleName = property("POM_ARTIFACT_ID")!!.toString()
compilations.configureEach {
compilerOptions.configure {
sourceMap.set(true)
Expand All @@ -58,7 +58,7 @@ kotlin {
wasmJs {
// Module name should be different from the one from JS
// otherwise IC tasks that start clashing different modules with the same module name
moduleName = project.name + "Wasm"
moduleName = property("POM_ARTIFACT_ID")!!.toString() + "Wasm"
browser()
nodejs()
}
Expand Down
Loading

0 comments on commit 517baab

Please sign in to comment.