1. 始める前に
前提条件
- Kotlin マルチプラットフォームに関する基礎知識。
- Kotlin を使用した経験
- Swift 構文に関する基礎知識。
- Xcode と iOS シミュレーターをインストール済み。
必要なもの
- Android Studio の最新の安定版。
- macOS システムを搭載した Mac マシン。
- Xcode 16.1 と、iOS 16.0 以降を搭載した iPhone シミュレーター。
学習内容
- Android アプリと iOS アプリ間で Room データベースを共有する方法。
2. セットアップする
使用を開始するには、以下のステップを実行してください。
- 次のターミナル コマンドを使用して GitHub リポジトリのクローンを作成します。
$ git clone https://github.com/android/codelab-android-kmp.git
または、リポジトリを ZIP ファイルとしてダウンロードすることもできます。
- Android Studio で、
migrate-roomプロジェクトを開きます。このプロジェクトには次のブランチが含まれています。
main: このプロジェクトのスターター コードが含まれています。これに変更を加えて Codelab を完了します。end: この Codelab の解答コードが含まれています。
main ブランチから始めて、Codelab の手順に沿って自分のペースで進めることをおすすめします。
- 解答コードを確認する場合は、このコマンドを実行します。
$ git clone -b end https://github.com/android/codelab-android-kmp.git
または、解答コードをダウンロードすることもできます。
3. サンプルアプリについて理解する
このチュートリアルは、ネイティブ フレームワーク(Android では Jetpack Compose、iOS では SwiftUi)で作成された Fruitties サンプルアプリで構成されています。
この Fruitties アプリには、主に次の 2 つの機能があります。
- フルーツ アイテムのリスト。各アイテムには、カートにアイテムを追加するためのボタンがあります。
- 上部に表示されるカート。追加されたフルーツの数とそれらの量が表示されます。

Android アプリ アーキテクチャ
Android アプリは、公式の Android アーキテクチャ ガイドラインに沿って、明確なモジュール構造を維持しています。

iOS アプリのアーキテクチャ

KMP 共有モジュール
このプロジェクトには、KMP 用の共有モジュールがあらかじめ設定されていますが、現在は空です。ご自身のプロジェクトに共有モジュールをまだ設定していない場合は、Getting Started with Kotlin Multiplatformの Codelab をご覧ください。
4. KMP 統合用に Room データベースを準備する
Room データベースのコードを Fruitties Android アプリから shared モジュールに移動する前に、アプリが Kotlin Multiplatform(KMP)Room API と互換性があることを確認する必要があります。このセクションでは、そのプロセスについて説明します。
主な更新の一つは、Android と iOS の両方と互換性がある SQLite ドライバの使用です。複数のプラットフォームで Room データベースの機能をサポートするには、BundledSQLiteDriver を使用します。このドライバは SQLite をアプリケーションに直接バンドルするため、Kotlin でのマルチプラットフォームの使用に適しています。詳しいガイダンスについては、Room KMP 移行ガイドをご覧ください。
依存関係を更新する
まず、room-runtime と sqlite-bundled の依存関係を libs.versions.toml ファイルに追加します。
# Add libraries
[libraries]
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "androidx-sqlite" }
次に、これらの依存関係を使用するように :androidApp モジュールの build.gradle.kts を更新し、libs.androidx.room.ktx の使用を削除します。
// Add
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
// Remove
implementation(libs.androidx.room.ktx)
そして、Android Studio でプロジェクトを同期します。
BundledSQLiteDriver のデータベース モジュールを変更する
次に、BundledSQLiteDriver を使用するように Android アプリのデータベース作成ロジックを変更し、Android で機能を維持すると同時に KMP との互換性を確保します。
androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.ktにあるDatabaseModule.ktファイルを開きます。- 次のスニペットのように
providesAppDatabaseメソッドを更新します。
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
...
@Provides
@Singleton
fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase {
val dbFile = context.getDatabasePath("sharedfruits.db")
return Room.databaseBuilder<AppDatabase>(context, dbFile.absolutePath)
.setDriver(BundledSQLiteDriver())
.build()
}
Android アプリをビルドして実行する
ネイティブ SQLite ドライバをバンドルされたドライバに切り替えたので、データベースを :shared モジュールに移行する前に、アプリがビルドされ、すべてが正しく動作することを確認しましょう。
5. データベース コードを :shared モジュールに移動する
このステップでは、Room データベースの設定を Android アプリから :shared モジュールに移行し、Android と iOS の両方からデータベースにアクセスできるようにします。
:shared モジュールの build.gradle.kts 構成を更新する
まず、Room マルチプラットフォームの依存関係を使用するように :shared モジュールの build.gradle.kts を更新します。
- KSP プラグインと Room プラグインを追加します。
plugins {
...
// TODO add KSP + ROOM plugins
alias(libs.plugins.ksp)
alias(libs.plugins.room)
}
commonMainブロックにroom-runtimeとsqlite-bundledの依存関係を追加します。
sourceSets {
commonMain {
// TODO Add KMP dependencies here
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
}
- 新しい最上位の
dependenciesブロックを追加して、各プラットフォーム ターゲットの KSP 構成を追加します。簡単にするため、ファイルの一番下に追加するだけで構いません。
// Should be its own top level block. For convenience, add at the bottom of the file
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
}
- 最上位に、
Roomスキーマの場所を設定する新しいブロックも追加します。
// Should be its own top level block. For convenience, add at the bottom of the file
room {
schemaDirectory("$projectDir/schemas")
}
- プロジェクトを Gradle 同期します。
Room スキーマを :shared モジュールに移動する
androidApp/schemas ディレクトリを src/ フォルダの横にある :shared モジュール ルート フォルダに移動します。
移動元: 
移動先: 
DAO とエンティティを移動する
必要な Gradle 依存関係を KMP 共有モジュールに追加したところで、DAO とエンティティを :androidApp モジュールから :shared モジュールに移動する必要があります。
具体的には、:shared モジュールの commonMain ソースセット内のそれぞれの場所にファイルを移動する必要があります。
Fruittie モデルを移動する
[Refactor → Move] 機能を使用すると、インポートを中断することなくモジュールを切り替えることができます。
androidApp/src/main/kotlin/.../model/Fruittie.ktファイルを見つけ、そのファイルを右クリックして、[Refactor → Move](または F6 キー)を選択します。:
- [Move] ダイアログで、[Destination directory] フィールドの横にある
...アイコンを選択します。
- [commonMain] ダイアログで commonMain ソースセットを選択し、[OK] をクリックします。[Show only existing source roots] チェックボックスを無効にすることが必要な場合があります。

- [Refactor] ボタンをクリックしてファイルを移動します。
CartItem および CartItemWithFruittie モデルを移動する
androidApp/.../model/CartItem.kt ファイルについて、次の手順を行う必要があります。
- ファイルを開き、
CartItemクラスを右クリックして、[Refactor > Move] を選択します。 - これにより、同じ [Move] ダイアログが開きますが、この場合は
CartItemWithFruittieメンバーのチェックボックスもオンにします。
Fruittie.ktファイルの場合と同様に、...アイコンを選択し、commonMainソースセットを選択して続行します。
DAO と AppDatabase を移動する
次のファイルについても同じ手順を行います(3 つすべてを同時に選択できます)。
androidApp/.../database/FruittieDao.ktandroidApp/.../database/CartDao.ktandroidApp/.../database/AppDatabase.kt
共有 AppDatabase を更新してプラットフォーム間で動作するようにする
データベース クラスを :shared モジュールに移動したところで、両方のプラットフォームで必要な実装を生成するようにクラスを調整する必要があります。
/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.ktファイルを開きます。RoomDatabaseConstructorの次の実装を追加します。
import androidx.room.RoomDatabaseConstructor
// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
AppDatabaseクラスに@ConstructedBy(AppDatabaseConstructor::class)アノテーションを付けます。
import androidx.room.ConstructedBy
@Database(
entities = [Fruittie::class, CartItem::class],
version = 1,
)
// TODO Add this line
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
...

データベースの作成を :shared モジュールに移動する
次に、Android 固有の Room の設定を :androidApp モジュールから :shared モジュールに移動します。これは、次のステップで :androidApp モジュールから Room 依存関係を削除するために必要です。
androidApp/.../di/DatabaseModule.ktファイルを見つけます。providesAppDatabase関数のコンテンツを選択して右クリックし、[Refactor] > [Extract Function to Scope] を選択します。
- メニューで
DatabaseModule.ktを選択します。
これにより、コンテンツがグローバルな appDatabase関数に移動します。Enter キーを押して関数名を確定します。
privateの可視性修飾子を削除して、関数を公開します。- 関数を
:sharedモジュールに移動するには、右クリックして [Refactor] > [Move] を選択します。 - [Move] ダイアログで、[Destination directory] フィールドの横にある [...] アイコンを選択します。

- [Choose Destination Directory] ダイアログで、[shared > androidMain] ソースセットを選択し、[/shared/src/androidMain/] フォルダを選択して、[OK] をクリックします。

- [To package] フィールドのサフィックスを
.diから.databaseに変更します。
- [Refactor] をクリックします。
:androidApp から不要なコードをクリーンアップする
この時点で、Room データベースをマルチプラットフォーム モジュールに移動しました。:androidApp モジュールでは Room の依存関係はいずれも必要ないため、削除できます。
:androidAppモジュールのbuild.gradle.ktsファイルを開きます。- 次のスニペットのように、依存関係と構成を削除します。
plugins {
// TODO Remove
alias(libs.plugins.room)
}
android {
// TODO Remove
ksp {
arg("room.generateKotlin", "true")
}
dependencies {
// TODO Keep room-runtime
implementation(libs.androidx.room.runtime)
// TODO Remove
implementation(libs.androidx.sqlite.bundled)
ksp(libs.androidx.room.compiler)
}
// TODO Remove
room {
schemaDirectory("$projectDir/schemas")
}
- プロジェクトを Gradle 同期します。
Android アプリをビルドして実行する
Fruitties Android アプリを実行して、アプリが正しく実行され、:shared モジュールのデータベースを使用していることを確認します。以前にカートにアイテムを追加したことがある場合は、Room データベースが :shared モジュールに移動しても、この時点で同じアイテムが表示されるはずです。
6. iOS で Room を使用する準備をする
iOS プラットフォーム向けに Room データベースを準備するには、次のステップで使用するサポートコードを :shared モジュールに設定する必要があります。
iOS アプリのデータベース作成を有効にする
まず、iOS 固有のデータベース ビルダーを追加します。
iosMainソースセットの:sharedモジュールにAppDatabase.ios.ktという名前の新しいファイルを追加します。
- 次のヘルパー関数を追加します。これらの関数は、iOS アプリが Room データベースのインスタンスを取得するために使用します。
package com.example.fruitties.kmptutorial.shared
import androidx.room.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import com.example.fruitties.kmptutorial.android.database.AppDatabase
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.ObjCObjectVar
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSError
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask
fun getPersistentDatabase(): AppDatabase {
val dbFilePath = documentDirectory() + "/" + "fruits.db"
return Room.databaseBuilder<AppDatabase>(name = dbFilePath)
.setDriver(BundledSQLiteDriver())
.build()
}
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
private fun documentDirectory(): String {
memScoped {
val errorPtr = alloc<ObjCObjectVar<NSError?>>()
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = errorPtr.ptr,
)
if (documentDirectory != null) {
return requireNotNull(documentDirectory.path) {
"""Couldn't determine the document directory.
URL $documentDirectory does not conform to RFC 1808.
""".trimIndent()
}
} else {
val error = errorPtr.value
val localizedDescription = error?.localizedDescription ?: "Unknown error occurred"
error("Couldn't determine document directory. Error: $localizedDescription")
}
}
}
Room エンティティに「Entity」接尾辞を追加する
Swift に Room エンティティのラッパーを追加するので、Room エンティティの名前はラッパーの名前とは異なるようにすることをおすすめします。@ObjCName アノテーションを使用して Room エンティティに Entity という接尾辞を追加することで、このことを確認します。
:shared モジュールの Fruittie.kt ファイルを開き、Fruittie エンティティに @ObjCName アノテーションを追加します。このアノテーションは試験運用版であるため、ファイルに @OptIn(ExperimentalObjC::class) アノテーションを追加することが必要な場合があります。
Fruittie.kt
import kotlin.experimental.ExperimentalObjCName
import kotlin.native.ObjCName
@OptIn(ExperimentalObjCName::class)
@Entity(indices = [Index(value = ["id"], unique = true)])
@ObjCName("FruittieEntity")
data class Fruittie(
...
)
次に、CartItem.kt ファイルの CartItem エンティティについても同様にします。
CartItem.kt
import kotlin.experimental.ExperimentalObjCName
import kotlin.native.ObjCName
@OptIn(ExperimentalObjCName::class)
@ObjCName("CartItemEntity")
@Entity(
foreignKeys = [
ForeignKey(
entity = Fruittie::class,
parentColumns = ["id"],
childColumns = ["id"],
onDelete = ForeignKey.CASCADE,
),
],
)
data class CartItem(@PrimaryKey val id: Long, val count: Int = 1)
7. iOS アプリで Room を使用する
iOS アプリは、Core Data を使用する既存のアプリです。このアプリは単なるプロトタイプであるため、この Codelab では、データベース内の既存のデータの移行について心配する必要はありません。製品版アプリを KMP に移行する場合は、現在の Core Data データベースを読み取り、移行後の初回起動時にそれらのアイテムを Room データベースに挿入する関数を記述する必要があります。
Xcode プロジェクトを開く
/iosApp/ フォルダに移動し、関連するアプリで Fruitties.xcodeproj を開いて、Xcode で iOS プロジェクトを開きます。

Core Data エンティティ クラスを削除する
まず、Core Data エンティティ クラスを削除して、後で作成するエンティティ ラッパーのスペースを確保する必要があります。アプリが Core Data に保存するデータの種類に応じて、Core Data エンティティを完全に削除するか、データ移行の目的のために保持するかを選択できます。このチュートリアルでは、既存のデータを移行する必要がないため、削除して問題ありません。
Xcode:
- Project Navigator に移動します。
- リソース フォルダに移動します。
Fruittiesファイルを開きます。- 各エンティティをクリックして削除します。

これらの変更をコードに反映するには、プロジェクトを削除して再ビルドします。
これにより、次のエラーが発生してビルドが失敗しますが、これは想定どおりです。

エンティティ ラッパーの作成
次に、Fruittie エンティティと CartItem エンティティのエンティティ ラッパーを作成し、Room エンティティと Core Data エンティティの API の違いをスムーズに処理します。
これらのラッパーにより、すぐに更新する必要があるコードの量を最小限に抑えて、Core Data から Room に移行することができます。今後は、これらのラッパーを Room エンティティへの直接アクセスに置き換えることを目標とする必要があります。
現時点では、FruittieEntity クラスのラッパーを作成し、任意のプロパティを指定します。
FruittieEntity ラッパーを作成する
Sources/Repositoryディレクトリに新しい Swift ファイルを作成するために、ディレクトリ名を右クリックして [Template...] から [New File] を選択します。

- 名前を
Fruittieに変更し、テスト対象ではなく Fruitties のターゲットのみが選択されるようにします。
- 新しい Fruittie ファイルに次のコードを追加します。
import sharedKit
struct Fruittie: Hashable {
let entity: FruittieEntity
var id: Int64 {
entity.id
}
var name: String? {
entity.name
}
var fullName: String? {
entity.fullName
}
}
Fruittie 構造体は FruittieEntity クラスをラップし、プロパティをオプションにして、エンティティのプロパティをパススルーします。また、Fruittie 構造体を Hashable プロトコルに準拠させ、SwiftUI の ForEach ビューで使用できるようにしています。
CartItemEntity ラッパーを作成する
次に、CartItemEntity クラスに同様のラッパーを作成します。
Sources/Repository ディレクトリに CartItem.swift という新しい Swift ファイルを作成します。
import sharedKit
struct CartItem: Hashable {
let entity: CartItemWithFruittie
let fruittie: Fruittie?
var id: Int64 {
entity.cartItem.id
}
var count: Int64 {
Int64(entity.cartItem.count)
}
init(entity: CartItemWithFruittie) {
self.entity = entity
self.fruittie = Fruittie(entity: entity.fruittie)
}
}
元の Core Data の CartItem クラスには Fruittie プロパティが設定されていたため、CartItem 構造体にも Fruittie プロパティを設定しました。CartItem クラスには省略可能なプロパティはありませんが、Room エンティティの count プロパティの型は異なります。
リポジトリを更新する
エンティティ ラッパーが設定されたため、Core Data ではなく Room を使用するように DefaultCartRepository と DefaultFruittieRepository を更新する必要があります。
DefaultCartRepository の更新
2 つのうち、よりシンプルな DefaultCartRepository クラスから始めましょう。
Sources/Repository ディレクトリの CartRepository.swift ファイルを開きます。
- まず、
CoreDataインポートをsharedKitに置き換えます。
import sharedKit
- 次に、
NSManagedObjectContextプロパティを削除してCartDaoプロパティに置き換えます。
// Remove
private let managedObjectContext: NSManagedObjectContext
// Replace with
private let cartDao: any CartDao
- 新しい
cartDaoプロパティを初期化するようinitを更新します。
init(cartDao: any CartDao) {
self.cartDao = cartDao
}
- 次に、
addToCartメソッドを更新します。このメソッドでは、Core Data から既存のカートアイテムを取得する必要がありましたが、Room の実装ではこの必要はありません。代わりに、新しいアイテムを挿入するか、既存のカートアイテムの数を増やします。
func addToCart(fruittie: Fruittie) async throws {
try await cartDao.insertOrIncreaseCount(fruittie: fruittie.entity)
}
- 最後に、
getCartItems()メソッドを更新します。このメソッドは、CartDaoでgetAll()メソッドを呼び出し、CartItemWithFruittieエンティティをCartItemラッパーにマッピングします。
func getCartItems() -> AsyncStream<[CartItem]> {
return cartDao.getAll().map { entities in
entities.map(CartItem.init(entity:))
}.eraseToStream()
}
DefaultFruittieRepository の更新
DefaultFruittieRepository クラスを移行するには、DefaultCartRepository の場合と同様の変更を適用します。
次のように FruittieRepository ファイルを更新します。
import ConcurrencyExtras
import sharedKit
protocol FruittieRepository {
func getData() -> AsyncStream<[Fruittie]>
}
class DefaultFruittieRepository: FruittieRepository {
private let fruittieDao: any FruittieDao
private let api: FruittieApi
init(fruittieDao: any FruittieDao, api: FruittieApi) {
self.fruittieDao = fruittieDao
self.api = api
}
func getData() -> AsyncStream<[Fruittie]> {
let dao = fruittieDao
Task {
let isEmpty = try await dao.count() == 0
if isEmpty {
let response = try await api.getData(pageNumber: 0)
let fruitties = response.feed.map {
FruittieEntity(
id: 0,
name: $0.name,
fullName: $0.fullName,
calories: ""
)
}
_ = try await dao.insert(fruitties: fruitties)
}
}
return dao.getAll().map { entities in
entities.map(Fruittie.init(entity:))
}.eraseToStream()
}
}
@FetchRequest プロパティ ラッパーを置き換える
SwiftUI ビューの @FetchRequest プロパティ ラッパーも置き換える必要があります。@FetchRequest プロパティ ラッパーは Core Data からデータを取得して変更を監視するためのものであるため、Room エンティティでは使用できません。代わりに、UIModel を使用してリポジトリのデータにアクセスします。
Sources/UI/CartView.swiftファイルでCartViewを開きます。- 実装を以下に置き換えます。
import SwiftUI
struct CartView : View {
@State
private var expanded = false
@ObservedObject
private(set) var uiModel: ContentViewModel
var body: some View {
if (uiModel.cartItems.isEmpty) {
Text("Cart is empty, add some items").padding()
} else {
HStack {
Text("Cart has \(uiModel.cartItems.count) items (\(uiModel.cartItems.reduce(0) { $0 + $1.count }))")
.padding()
Spacer()
Button {
expanded.toggle()
} label: {
if (expanded) {
Text("collapse")
} else {
Text("expand")
}
}
.padding()
}
if (expanded) {
VStack {
ForEach(uiModel.cartItems, id: \.self) { item in
Text("\(item.fruittie!.name!): \(item.count)")
}
}
}
}
}
}
ContentView を更新する
FruittieUIModel を CartView に渡すように Sources/View/ContentView.swift ファイルの ContentView を更新します。
CartView(uiModel: uiModel)
DataController を更新する
iOS アプリの DataController クラスは、Core Data スタックの設定を担当します。Core Data から移行しているため、Room データベースを初期化するように DataController を更新する必要があります。
Sources/DatabaseでDataController.swiftファイルを開きます。sharedKitのインポートを追加します。CoreDataのインポートを削除します。DataControllerクラスで Room データベースをインスタンス化します。- 最後に、
DataControllerイニシャライザからloadPersistentStoresメソッドの呼び出しを削除します。
最終クラスは次のようになるはずです。
import Combine
import sharedKit
class DataController: ObservableObject {
let database = getPersistentDatabase()
init() {}
}
依存関係インジェクションの更新
iOS アプリの AppContainer クラスは、依存関係グラフの初期化を行います。Core Data ではなく Room を使用するようにリポジトリを更新したため、Room DAO をリポジトリに渡すように AppContainer を更新する必要があります。
Sources/DIフォルダでAppContainer.swiftを開きます。sharedKitのインポートを追加します。AppContainerクラスからmanagedObjectContextプロパティを削除します。DataControllerによって付与されるAppDatabaseインスタンスの Room DAO を渡して、DefaultFruittieRepositoryとDefaultCartRepositoryの初期化を変更します。
完了すると、クラスは次のようになります。
import Combine
import Foundation
import sharedKit
class AppContainer: ObservableObject {
let dataController: DataController
let api: FruittieApi
let fruittieRepository: FruittieRepository
let cartRepository: CartRepository
init() {
dataController = DataController()
api = FruittieNetworkApi(
apiUrl: URL(
string:
"https://android.github.io/kotlin-multiplatform-samples/fruitties-api"
)!)
fruittieRepository = DefaultFruittieRepository(
fruittieDao: dataController.database.fruittieDao(),
api: api
)
cartRepository = DefaultCartRepository(
cartDao: dataController.database.cartDao()
)
}
}
最後に、managedObjectContext を削除するように main.swift の FruittiesApp を更新します。
struct FruittiesApp: App {
@StateObject
private var appContainer = AppContainer()
var body: some Scene {
WindowGroup {
ContentView(appContainer: appContainer)
}
}
}
iOS アプリをビルドして実行する
最後に、⌘R を押してアプリをビルドして実行すると、Core Data から Room に移行されたデータベースでアプリが起動します。

8. 完了
これで完了です。Room KMP を使用して、スタンドアロンの Android アプリと iOS アプリが共有データレイヤに正常に移行されました。
参考までに、アプリのアーキテクチャを比較して、達成された成果をご確認ください。
変更前
Android | iOS |
|
|
移行後のアーキテクチャ

関連リンク
- KMP をサポートするその他の Jetpack ライブラリについて学習する
- Room KMP のドキュメント
- SQLite KMP のドキュメント
- Kotlin Multiplatform の公式ドキュメント