1. 始める前に
前提条件
- Kotlin マルチプラットフォームに関する基礎知識。
- Kotlin の使用経験。
- Swift 構文に関する基礎知識。
- Xcode と iOS シミュレーターをインストール済み。
必要なもの
- Android Studio の最新の安定版。
- macOS システムを搭載した Mac マシン。
- Xcode 16.1 と、iOS 16.0 以降を搭載した iPhone シミュレーター。
学習内容
- Android アプリと iOS アプリ間で Room Database を共有する方法。
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 用の共有モジュールがすでに設定されていますが、現在は空です。プロジェクトに共有モジュールがまだ設定されていない場合は、Kotlin マルチプラットフォームのスタートガイドの Codelab から始めてください。
4. KMP 統合用に Room データベースを準備する
Room データベース コードを Fruitties Android アプリから shared
モジュールに移動する前に、アプリが Kotlin マルチプラットフォーム(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 のデータベース モジュールを変更する
次に、Android アプリのデータベース作成ロジックを変更して BundledSQLiteDriver
を使用するようにし、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)
}
room-runtime
とsqlite-bundled
の依存関係をcommonMain
ブロックに追加します。
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] フィールドの横にある
...
アイコンを選択します。 - [Choose Destination Directory] ダイアログで 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.kt
androidApp/.../database/CartDao.kt
androidApp/.../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)
@Serializable
@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
を更新する
Sources/View/ContentView.swift
ファイルの ContentView
を更新して、FruittieUIModel
を CartView
に渡します。
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()
)
}
}
最後に、main.swift
の FruittiesApp
を更新して managedObjectContext
を削除します。
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 マルチプラットフォーム ドキュメントを確認する。