Kotlin Multiplatform と App Group の指定

Kotlin Mutliplatform を利用して iOS 向けにもアプリを展開する際に、その中でも特に Widget も含む場合には **App Group** を考慮する必要があります。 iOS Widget データ共有の基本については以下の記事も参照ください。

https://qiita.com/ykws/items/f981d5250baf7b27b34f

Kotlin Multiplatform に対応したデータベースには、現在主に二つ候補があります。

- Room
- SQLDelight

key-value の保存形式で十分な場合は、 **multiplatform-settings** も候補になります。こちらについてはこの記事では扱いません。後述の参考記事に詳しい解説があります。

https://github.com/russhwolf/multiplatform-settings

# Room
Room は `2.7.0-alpha01` から Kotlin Multiplatform に対応しています。
詳しい実装は Google から公式のガイドが用意されています。

https://developer.android.com/kotlin/multiplatform/room

このガイドでは **App Group** には対応していないため、 `getDatabaseBuilder` を書き換える必要があります。

```diff_kotlin
// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = documentDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
    )
}

private fun documentDirectory(): String {
- val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
+ val documentDirectory = NSFileManager.defaultManager.containerURLForSecurityApplicationGroupIdentifier(
+   groupIdentifier = "APP_GROUP_IDENTIFIER",
-   directory = NSDocumentDirectory,
-   inDomain = NSUserDomainMask,
-   appropriateForURL = null,
-   create = false,
-   error = null,
  )
  return requireNotNull(documentDirectory?.path)
}
```

`NSFileManager` を利用するのは変わらないですが、 `URLForDirectory` ではなく、 `containerURLForSecurityApplicationGroupIdentifier` で `APP_GROUP` を指定して、アプリ間で共有可能なディレクトリを取得します。

https://developer.apple.com/documentation/foundation/nsfilemanager/1412643-containerurlforsecurityapplicati

# SQLDelight

SQLDelight を利用した公式のガイドは JetBrains から提供されています。

https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-ktor-sqldelight.html

こちらのガイドも **App Group** には対応していないため、 `createDriver` を書き換える必要があります。

```diff_kotlin
// shared/src/iosMain/kotlin/DatabaseDriverFactory.kt

class IOSDatabaseDriverFactory : DatabaseDriverFactory {
    override fun createDriver(): SqlDriver {
-        return NativeSqliteDriver(AppDatabase.Schema, "launch.db")
+        return NativeSqliteDriver(
+            name = "launch.db",
+            version = AppDatabase.Schema.version.toInt(),
+            extendedConfig = DatabaseConfiguration.Extended(
+                basePath = NSFileManager
+                    .defaultManager
+                    .containerURLForSecurityApplicationGroupIdentifier(
+                        groupIdentifier = "APP_GROUP_IDENTIFIER"
+                    )?.absoluteString
+        )
    }
}
```

方針としては Room と同様に、`containerURLForSecurityApplicationGroupIdentifier` で `APP_GROUP` を指定して、アプリ間で共有可能なディレクトリを取得します。
`DatabaseConfiguration.Extended` を介してそのパスを渡すようにします。
以下の Issue で会話されていました。

https://github.com/sqldelight/sqldelight/issues/1787


Room の `DatabaseConfiguration` よりも拡張された API が SQLDelight では提供されていました。

https://developer.android.com/reference/kotlin/androidx/room/DatabaseConfiguration

## 参考
SQLDelight の **App Group** の指定は Kotlin Multiplatform で App Clip に対応した以下の記事が大変参考になりました。　key-value 形式の  **multiplatform-settings** の解説も含まれています。

https://qiita.com/shumpei_nagata/items/7dcbd065ffbcf7ca94da#sqlite


# 注意点

:::note warn
Xcode の Signing & Capabilities では App Group が省略表示されている可能性があります
:::

引き継いだり途中参画するプロジェクトにおいて、自身で作成していない App Group を確認する際は注意が必要です。

例えば、次のように表示されたりします。

![スクリーンショット 2024-12-19 2.19.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31787/9c5a13c9-6a27-9fd4-0969-f27ef78ddc8e.png)

ここでの正式名称は `group.com.example.long.long.long.app.group.name` です。 `entitlements` から正しい App Group を参照できます。

![スクリーンショット 2024-12-19 2.20.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31787/c1bfb717-d15e-2b3c-61d8-2a2adb102839.png)

# まとめ

たったこれだけのことで Kotlin Multiplatform で保存したデータを iOS 側の App と Widget でも共有できるようになります！

- App Group を定義する
- App Group を指定したパスにデータベースファイルを作成する