Kotlin MultipatformとFirestoreでWebページ

# 概要(日記)

Kotlin Multiplatform (Kotlin/js)とfirebase-kotlin-SDKでプラウザアプリの骨組みを作る。

# 前提
- Ubuntu 22.04 on Windwos 11 wsl2
- Firebaseプロジェクト・Firestoreが有効であること
- 認証無し。実験用

# Step 1: 空のアプリ作成
[参照](https://kotlinlang.org/docs/js-project-setup.html)
```build.gradle.kts
plugins {
    kotlin("multiplatform") version "2.0.0"
}

repositories {
    mavenCentral()
}

kotlin {
    js {
        browser{}
        binaries.executable()
    }
}
```
```src/jsMain/kotlin/Main.kt
fun main() {
    println("Hello, World!")
}
```
```src/jsMain/index.html
<script src="KtFirestore.js"></script>
```
`src="KtFirestore.js"`はProject名(デフォルトでプロジェクトフォルダ名)に置き換え

```sh:実行
sh gradlew jsRun
```
ブラウザが起動し空のページが表示される。開発者ツールでConsole上に`Hello, World!`が表示されていることを確認。

# Step 2: Firebase Kotlin SDK 組み込み
[参照](https://firebaseopensource.com/projects/gitliveapp/firebase-kotlin-sdk)

Firestore SDKは大きく2通り
- Admin SDK: サーバ上で動作するFirestoreアプリ用。サーバ上でAPIを提供する場合
- モバイル/Web SDK: デバイス/ブラウザから直接(APIを介さず)DBにアクセスする場合

プラウザアプリならWeb SDKを使用するのだが、今回せっかくなのでmultiplatformに対応する[firebase-kotlin-SDK](https://firebaseopensource.com/projects/gitliveapp/firebase-kotlin-sdk/)を試してみた。
Webページはkotlinx.htmlを使ってHTMLを手書き。

```build.gradle.kts
//...
kotlin {
    js {
        browser {}
        binaries.executable()
    }
    sourceSets {
        jsMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
            implementation("dev.gitlive:firebase-firestore:1.13.0") // https://mvnrepository.com/artifact/dev.gitlive/firebase-firestore
            implementation("org.jetbrains.kotlinx:kotlinx-html:0.11.0") // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-html
        }
    }
}
```
```src/jsMain/resources/index.html
<body></body>
<script src="プロジェクト名.js"></script>
```

```src/jsMain/kotlin/Main.kt
val options = FirebaseOptions(
    apiKey = "xxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxx",
    projectId = "xxxxxx",
    databaseUrl = "https://xxxxxxxxxxxxx.firebaseio.com",
    applicationId = "1:xxxxxxxxxxx:web:xxxxxxxxxxxxxxxxxxxx",
)

@OptIn(DelicateCoroutinesApi::class)
suspend fun main() {
    val app = Firebase.initialize(Unit, options)
    val db = Firebase.firestore(app)
    db.settings = firestoreSettings(db.settings) { cacheSettings = persistentCacheSettings { } }

    fun addItem(item: String) = GlobalScope.launch { db.collection("tmp").add(mapOf("action" to item)) }
    fun delItem(id: String) = GlobalScope.launch { db.collection("tmp").document(id).delete() }

    val action = document.create.input(name = "name")
    val book = document.create.table()
    val body = document.body ?: error("body is null")
    body.append(book)
    db.collection("tmp").snapshots.collect { qs ->
        book.innerHTML = ""
        book.tHead = document.create.thead { tr { td {};td { +"To do List" } } }
        book.insertRow().apply {
            insertCell().append { button { +"Add" }.onclick = { addItem(action.value) } }
            insertCell().append(action)
        }
        qs.documents.forEach { ds ->
            book.insertRow().apply {
                insertCell().append { button { +"Del" }.onclick = { delItem(ds.id) } }
                insertCell().apply { textContent = ds.get<String?>("action") ?: "[No Action]" }
            }
        }
    }
}
```
apiKeyなどの接続情報は Firebaseポータル→プロジェクトの設定 を参照。
※実際のコードではapiKeyをコミットしてしまわないよう注意。

[ソースコード](https://github.com/kyoya-p/samples/tree/afc2e684ecb544737aecc52275e664c980bae4c1/2024/KtMPs/KtHTMLFirestoreApp)

# 資料

https://firebaseopensource.com/projects/gitliveapp/firebase-kotlin-sdk/

https://github.com/GitLiveApp/firebase-kotlin-sdk

https://firebase.google.com/docs/firestore/quickstart