Skip to content

Commit e63afab

Browse files
committed
Add API emulation
1 parent e4c122e commit e63afab

File tree

5 files changed

+82
-8
lines changed

5 files changed

+82
-8
lines changed

build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ kotlin {
8484
implementation(compose.material3)
8585
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
8686
implementation(compose.components.resources)
87+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2")
8788
}
8889
}
8990
}
@@ -102,9 +103,9 @@ tasks.named<Jar>("jvmJar").configure {
102103
val taskName = if (project.hasProperty("isProduction")
103104
|| project.gradle.startParameter.taskNames.contains("installDist")
104105
) {
105-
"jsBrowserProductionWebpack"
106+
"wasmJsBrowserProductionWebpack"
106107
} else {
107-
"jsBrowserDevelopmentWebpack"
108+
"wasmJsBrowserDevelopmentWebpack"
108109
}
109110
val webpackTask = tasks.named<KotlinWebpack>(taskName)
110111
dependsOn(webpackTask)

src/jsMain/kotlin/Api.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import io.ktor.client.plugins.contentnegotiation.*
55
import io.ktor.client.request.*
66
import io.ktor.serialization.kotlinx.json.*
77

8-
import kotlinx.browser.window
9-
108
val jsonClient = HttpClient {
119
install(ContentNegotiation) {
1210
json()

src/wasmJsMain/kotlin/Api.kt

Whitespace-only changes.

src/wasmJsMain/kotlin/HttpClient.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Until Ktor starts work with wasmJs
2+
import kotlinx.serialization.encodeToString
3+
import kotlinx.serialization.json.Json
4+
import kotlin.coroutines.resume
5+
import kotlin.coroutines.resumeWithException
6+
import kotlin.coroutines.suspendCoroutine
7+
import kotlin.js.Promise
8+
9+
class HttpClient {
10+
suspend inline fun <reified T> get(url: String): T =
11+
Json.decodeFromString<T>(fetchAsync(url))
12+
13+
suspend inline fun <reified T> post(url: String, body: T) {
14+
fetchAsync(url, FetchOptions(method = "POST", body = Json.encodeToString(body), headers = mapOf("Content-Type" to "application/json")))
15+
}
16+
17+
suspend inline fun delete(url: String) {
18+
fetchAsync(url, FetchOptions(method = "DELETE"))
19+
}
20+
}
21+
22+
private fun buildFetchOptions(method: JsAny?, body: JsAny?, headers: JsAny?): JsAny =
23+
js("{ method: method, body: body, headers: headers }")
24+
25+
private fun fetch(url: String, opts: JsAny? = null): Promise<JsString> =
26+
js("fetch(url, opts).then(x => x.text())")
27+
28+
private fun <T: JsAny?> createJsArray(): JsArray<T> = js("[]")
29+
30+
private fun Map<String, String>.toJsEntries(): JsArray<JsArray<JsString>> {
31+
val result = createJsArray<JsArray<JsString>>()
32+
for ((key, value) in this) {
33+
val entry = createJsArray<JsString>().apply {
34+
set(0, key.toJsString())
35+
set(1, value.toJsString())
36+
}
37+
result[result.length] = entry
38+
}
39+
return result
40+
}
41+
42+
suspend fun fetchAsync(url: String, opts: FetchOptions? = null): String {
43+
val promise = fetch(url, buildFetchOptions(opts?.method?.toJsString(), opts?.body?.toJsString(), opts?.headers?.toJsEntries()))
44+
return suspendCoroutine { cont ->
45+
promise.then(
46+
onFulfilled = { cont.resume(it.toString()).toJsReference() },
47+
onRejected = { cont.resumeWithException(IllegalStateException("Promise error: $it")).toJsReference() }
48+
)
49+
}
50+
}
51+
52+
data class FetchOptions(
53+
val method: String? = null,
54+
val body: String? = null,
55+
val headers: Map<String, String> = emptyMap(),
56+
)
57+

src/wasmJsMain/kotlin/ViewModel.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
1-
import androidx.compose.runtime.mutableStateListOf
1+
import androidx.compose.runtime.getValue
2+
import androidx.compose.runtime.mutableStateOf
3+
import androidx.compose.runtime.setValue
4+
import kotlinx.coroutines.DelicateCoroutinesApi
5+
import kotlinx.coroutines.GlobalScope
6+
import kotlinx.coroutines.promise
27

8+
@OptIn(DelicateCoroutinesApi::class)
39
class ShoppingListViewModel {
4-
val shoppingList = mutableStateListOf<ShoppingListItem>()
10+
private val jsonClient = HttpClient()
11+
12+
var shoppingList by mutableStateOf(listOf<ShoppingListItem>())
13+
private set
514

615
fun fetchShoppingList() {
16+
GlobalScope.promise {
17+
shoppingList = jsonClient.get(ShoppingListItem.path)
18+
}
719
}
820

921
fun addShoppingListItem(shoppingListItem: ShoppingListItem) {
10-
shoppingList.add(shoppingListItem)
22+
GlobalScope.promise {
23+
jsonClient.post(ShoppingListItem.path, shoppingListItem)
24+
shoppingList += shoppingListItem
25+
}
1126
}
1227

1328
fun deleteShoppingListItem(shoppingListItem: ShoppingListItem) {
14-
shoppingList.remove(shoppingListItem)
29+
GlobalScope.promise {
30+
jsonClient.delete(ShoppingListItem.path + "/${shoppingListItem.id}")
31+
shoppingList -= shoppingListItem
32+
}
1533
}
1634
}

0 commit comments

Comments
 (0)