將現有應用程式遷移至 Room KMP

1. 事前準備

必要條件

軟硬體需求

課程內容

  • 如何在 Android 應用程式和 iOS 應用程式之間共用 Room 資料庫。

2. 做好準備

首先,請按照下列步驟操作:

  1. 使用下列終端機指令複製 GitHub 存放區:
$ git clone https://github.com/android/codelab-android-kmp.git

您也可以透過 ZIP 檔案下載存放區:

  1. Android Studio 中開啟 migrate-room 專案,該專案含有下列分支版本:
  • main:包含此專案的範例程式碼,您將修改這些程式碼來完成本程式碼研究室。
  • end:含有本程式碼研究室的解決方案程式碼。

建議您先從 main 分支版本開始著手,依自己的步調逐步完成本程式碼實驗室。

  1. 如要查看解決方案程式碼,請執行以下指令:
$ git clone -b end https://github.com/android/codelab-android-kmp.git

或者,您也可以下載解決方案程式碼:

3. 瞭解範例應用程式

本教學課程包含以原生架構 (Android 上的 Jetpack Compose、iOS 上的 SwiftUi) 建構的 Fruities 範例應用程式。

Fruitties 應用程式提供兩項主要功能:

  • 「水果」項目清單,每個項目都有一個按鈕,可將該項目加入購物車。
  • 頂端顯示「購物車」,顯示加入的水果數量。

4a7f262b015d7f78.png

Android 應用程式架構

Android 應用程式遵守官方的 Android 架構指南,維持清楚的模組化結構。

整合 KMP 前的 Android 應用程式架構圖

iOS 應用程式架構

整合 KMP 前的 iOS 應用程式架構圖

KMP 共用模組

這個專案已設定 KMP 共用模組,但目前為空白。如果您的專案尚未設定共用模組,請先完成「開始使用 Kotlin Multiplatform」程式碼實驗室。

4. 準備 Room 資料庫用於 KMP 整合作業

將 Room 資料庫程式碼從 Fruitties Android 應用程式移至 shared 模組前,您必須確保應用程式與 Kotlin Multiplatform (KMP) Room API 相容。本節將逐步說明這項程序。

其中一個重大更新是使用與 Android 和 iOS 相容的 SQLite 驅動程式。如要在多個平台上支援 Room 資料庫功能,您可以使用 BundledSQLiteDriver。這個驅動程式會將 SQLite 直接套用至應用程式,適合 Kotlin 多平台用途。如需詳細指南,請參閱 Room KMP 遷移指南

更新依附元件

首先,請將 room-runtimesqlite-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,讓應用程式與 KMP 相容,同時維持在 Android 上的功能。

  1. 開啟位於 androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.ktDatabaseModule.kt 檔案
  2. 更新 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 設定

首先,請更新 :shared 模組的 build.gradle.kts,以便使用 Room 多平台依附元件。

  1. 新增 KSP 和 Room 外掛程式:
plugins {
   ...
   // TODO add KSP + ROOM plugins
   alias(libs.plugins.ksp)
   alias(libs.plugins.room)
}
  1. room-runtimesqlite-bundled 依附元件新增至 commonMain 區塊:
sourceSets {
    commonMain {
        // TODO Add KMP dependencies here
        implementation(libs.androidx.room.runtime)
        implementation(libs.androidx.sqlite.bundled)
    }
}
  1. 加入頂層 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)
}
  1. 同樣在頂層新增區塊,設定 Room 結構定義位置:
// Should be its own top level block. For convenience, add at the bottom of the file
room {
   schemaDirectory("$projectDir/schemas")
}
  1. Gradle 同步處理專案

將 Room 結構定義移至 :shared 模組

androidApp/schemas 目錄移至 src/ 資料夾旁的 :shared 模組根資料夾:

來自:e1ee37a3f3a10b35.png

目的地:ba3c9eb617828bac.png

移動 DAO 和實體

現在您已將必要的 Gradle 依附元件加入 KMP 共用模組,需要將 DAO 和實體從 :androidApp 模組移至 :shared 模組。

這會將檔案移至 :shared 模組中 commonMain 來源集內的各自位置。

移動 Fruittie 模型

您可以依序點選「Refactor」>「Move」來切換模組,而不會中斷匯入作業:

  1. 找出 androidApp/src/main/kotlin/.../model/Fruittie.kt 檔案,在檔案上按一下滑鼠右鍵,依序選取「Refactor」>「Move」 (或按 F6 鍵)c893e12b8bf683ae.png
  2. 在「Move」對話方塊中,選取「Destination directory」欄位旁的 ... 圖示。1d51c3a410e8f2c3.png
  3. 在「Choose Destination Directory」對話方塊中選取「commonMain」來源集,然後按一下「OK」。您可能需要停用「Show only existing source roots」核取方塊。f61561feb28a6445.png
  4. 按一下「Refactor」按鈕,即可移動檔案。

移動 CartItemCartItemWithFruittie 模型

針對檔案 androidApp/.../model/CartItem.kt,您需要執行下列步驟:

  1. 開啟檔案,在 CartItem 類別上按一下滑鼠右鍵,依序選取「Refactor」>「Move」
  2. 這會開啟相同的「Move」對話方塊,但在這種情況下,您也必須勾選 CartItemWithFruittie 成員的核取方塊。
  3. a25022cce5cee5e0.png 接著,依序選取 ... 圖示和 commonMain 來源集,就像處理 Fruittie.kt 檔案時一樣。

移動 DAO 和 AppDatabase

對下列檔案執行相同步驟 (可同時選取所有檔案):

  • androidApp/.../database/FruittieDao.kt
  • androidApp/.../database/CartDao.kt
  • androidApp/.../database/AppDatabase.kt

更新共用 AppDatabase以便在各平台上運作

資料庫類別已移至 :shared 模組,因此您需要調整這些類別,以便在兩個平台上產生必要的實作項目。

  1. 開啟 /shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt 檔案。
  2. 新增下列 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
}
  1. 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() {
...

410a3c0c656b6499.png

將資料庫建立作業移至 :shared 模組

接下來,您要將 Android 專屬的 Room 設定從 :androidApp 模組移至 :shared 模組。這是必要步驟,因為您會在下一個步驟中從 :androidApp 模組移除 Room 依附元件。

  1. 找出 androidApp/.../di/DatabaseModule.kt 檔案。
  2. 選取 providesAppDatabase 函式的內容並按一下滑鼠右鍵,依序點選「Refactor」>「Extract Function to Scope」da4d97319f9a0e8c.png
  3. 從選單中選取 DatabaseModule.kt5e540a1eec6e3493.png 這會將內容移至全域 appDatabase 函式。按下 Enter 鍵確認函式名稱。e2fb113d66704a36.png
  4. 移除 private 瀏覽權限修飾符,將函式設為公開。
  5. 按一下滑鼠右鍵,依序點選「Refactor」>「Move」,將函式移至 :shared 模組。
  6. 在「Move」對話方塊中,選取「Destination directory」欄位旁的「...」圖示。e2101005f2ef4747.png
  7. 在「Choose Destination Directory」對話方塊中,依序選取「shared > androidMain」來源集 >「/shared/src/androidMain/」資料夾,按一下「OK」73d244941c68dc85.png
  8. 將「To package」欄位中的後置字串從 .di 變更為 .databaseac5cf30d32871e2c.png
  9. 按一下「Refactor」

清理 :androidApp 中不需要的程式碼

此時,您已將 Room 資料庫移至多平台模組,且 :androidApp 模組不需要使用任何 Room 依附元件,因此可以將這些元件移除。

  1. 開啟 :androidApp 模組中的 build.gradle.kts 檔案。
  2. 如以下程式碼片段所示,移除依附元件和設定:
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")
}
  1. Gradle 同步處理專案。

建構並執行 Android 應用程式

執行 Fruitties Android 應用程式,確認應用程式能正常運作,並使用 :shared 模組中的資料庫。如果您先前曾加入購物車商品,即使 Room 資料庫現在位於 :shared 模組中,此時應仍可看到相同的商品。

6. 準備在 iOS 上使用 Room

如要進一步準備 iOS 平台適用的 Room 資料庫,您需要在 :shared 模組中設定一些支援程式碼,以便在下一個步驟中使用。

啟用 iOS 應用程式的資料庫建立功能

首先,請新增 iOS 專屬資料庫建構工具。

  1. iosMain 來源集中的 :shared 模組中新增名為 AppDatabase.ios.kt 的新檔案:dcb46ba560298865.png
  2. 新增下列輔助函式。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 檔案,將 @ObjCName 註解新增至 Fruittie 實體。由於此註解為實驗功能,您可能需要在檔案中新增 @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 的現有應用程式。在本程式碼實驗室中,您不必擔心遷移資料庫中的任何現有資料,因為這只是原型應用程式。如果您要將正式版應用程式遷移至 KMP,就必須編寫函式來讀取目前的 Core Data 資料庫,並在遷移後首次啟動時,將這些項目插入 Room 資料庫。

開啟 Xcode 專案

Xcode 中開啟 iOS 專案,方法是前往 /iosApp/ 資料夾,在相關應用程式中開啟 Fruitties.xcodeproj

54836291a243ebe9.png

移除 Core Data 實體類別

首先,您必須移除 Core Data 實體類別,為稍後建立的實體包裝函式騰出空間。視應用程式在 Core Data 中儲存的資料類型而定,您可以完全移除 Core Data 實體,也可以保留實體以便遷移資料。在本教學課程中,您不需要遷移任何現有資料,因此可以直接移除。

在 Xcode 中:

  1. 前往「Project Navigator」。
  2. 前往「Resources」資料夾。
  3. 開啟 Fruitties 檔案。
  4. 逐一點選並刪除每個實體。

7ad742d991d76b1c.png

如要在程式碼中使用這些變更,請清除專案再重新建構。

這麼做應該會導致建構作業失敗,並顯示下列錯誤。

e3e107bf0387eeab.png

建立實體包裝函式

接下來為 FruittieCartItem 實體建立實體包裝函式,以便順利處理 Room 和 Core Data 實體之間的 API 差異。

這些包裝函式會盡量減少需要立即更新的程式碼數量,協助我們從 Core Data 轉換至 Room。建議您設法在日後以直接存取 Room 實體的方式,來取代這些包裝函式。

現在為 FruittieEntity 類別建立包裝函式,並提供選用屬性。

建立 FruittieEntity 包裝函式

  1. Sources/Repository 目錄中建立新的 Swift 檔案,方法是在目錄名稱上按一下滑鼠右鍵,選取「New File from Template...」cce140b2fb3c2da8.png 6a0d4fa4292ddd4f.png
  2. 將檔案命名為 Fruittie,並確認只選取 Fruities 目標,而非測試目標。827b9019b0a32352.png
  3. 將下列程式碼新增至新的 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 類別沒有任何選用屬性,但 count 屬性在 Room 實體中具有不同的類型。

更新存放區

現在實體包裝函式已就位,您需要更新 DefaultCartRepositoryDefaultFruittieRepository,以便使用 Room 而非 Core Data。

更新「DefaultCartRepository

我們先從 DefaultCartRepository 類別開始,因為它是兩者中較簡單的類別。

Sources/Repository 目錄中開啟 CartRepository.swift 檔案。

  1. 首先,請將 CoreData 匯入項目替換為 sharedKit
import sharedKit
  1. 接著,請移除 NSManagedObjectContext 屬性,並以 CartDao 屬性取代:
// Remove
private let managedObjectContext: NSManagedObjectContext

// Replace with
private let cartDao: any CartDao
  1. 更新 init 建構函式,初始化新的 cartDao 屬性:
init(cartDao: any CartDao) {
    self.cartDao = cartDao
}
  1. 接著,請更新 addToCart 方法。這個方法需要從 Core Data 提取現有的購物車商品,但 Room 實作項目不需要這麼做,而是插入新項目,或增加現有購物車項目的數量。
func addToCart(fruittie: Fruittie) async throws {
    try await cartDao.insertOrIncreaseCount(fruittie: fruittie.entity)
}
  1. 最後,請更新 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 存取存放區中的資料。

  1. 開啟 Sources/UI/CartView.swift 檔案中的 CartView
  2. 將實作項目取代為以下內容:
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,因此需要更新 DataController,才能初始化 Room 資料庫。

  1. 開啟 Sources/Database 中的 DataController.swift 檔案。
  2. 新增 sharedKit 匯入作業。
  3. 移除 CoreData 匯入作業。
  4. DataController 類別中的 Room 資料庫例項化。
  5. 最後,請從 DataController 初始化器中移除 loadPersistentStores 方法呼叫。

最終類別應如下所示:

import Combine
import sharedKit

class DataController: ObservableObject {
    let database = getPersistentDatabase()
    init() {}
}

更新依附元件插入功能

iOS 應用程式中的 AppContainer 類別負責初始化依附元件圖。由於我們更新了存放區,改用 Room 而非 Core Data,因此需要更新 AppContainer,將 Room DAO 傳遞至存放區。

  1. 開啟 Sources/DI 資料夾中的 AppContainer.swift
  2. 新增 sharedKit 匯入作業。
  3. AppContainer 類別中移除 managedObjectContext 屬性。
  4. DataController 提供的 AppDatabase 例項傳入 Room DAO,變更 DefaultFruittieRepositoryDefaultCartRepository 初始化作業。

完成後,類別會如下所示:

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 開始。

5d2ae9438747f8f6.png

8. 恭喜

恭喜!您已成功使用 Room KMP 將獨立的 Android 和 iOS 應用程式遷移至共用資料層。

以下提供應用程式架構比較表,供您參考:

變更前

Android

iOS

整合 KMP 前的 Android 應用程式架構圖

整合 KMP 前的 iOS 應用程式架構圖

遷移後架構

bcd8c29b00f67c19.png

瞭解詳情