Memigrasikan aplikasi yang ada ke Room KMP

1. Sebelum memulai

Prasyarat

Yang Anda perlukan

Yang Anda pelajari

  • Cara membagikan Database Room antara aplikasi Android dan aplikasi iOS.

2. Memulai persiapan

Untuk memulai, ikuti langkah-langkah ini:

  1. Clone repositori GitHub dengan perintah terminal berikut:
$ git clone https://github.com/android/codelab-android-kmp.git

Atau, Anda dapat mendownload repositori sebagai file zip:

  1. Di Android Studio, buka project migrate-room, yang berisi cabang berikut:
  • main: Berisi kode awal untuk project ini, tempat Anda membuat perubahan untuk menyelesaikan codelab.
  • end: Berisi kode solusi untuk codelab ini.

Sebaiknya Anda memulai dengan cabang main dan mengikuti codelab langkah demi langkah sesuai kemampuan Anda.

  1. Jika Anda ingin melihat kode solusi, jalankan perintah ini:
$ git clone -b end https://github.com/android/codelab-android-kmp.git

Selain itu, Anda dapat mendownload kode solusi:

3. Memahami aplikasi contoh

Tutorial ini terdiri dari aplikasi contoh Fruitties yang dibangun dalam framework native (Jetpack Compose di Android, SwiftUi di iOS).

Aplikasi Fruitties menawarkan dua fitur utama:

  • Daftar item Buah, masing-masing dengan tombol untuk menambahkan item ke Keranjang.
  • Keranjang ditampilkan di bagian atas, yang menunjukkan jumlah buah yang telah ditambahkan dan jumlahnya.

4a7f262b015d7f78.png

Arsitektur aplikasi Android

Aplikasi Android mengikuti panduan arsitektur Android resmi untuk mempertahankan struktur yang jelas dan modular.

Diagram arsitektur aplikasi Android sebelum integrasi KMP

Arsitektur aplikasi iOS

Diagram arsitektur aplikasi iOS sebelum integrasi KMP

Modul Bersama KMP

Project ini telah disiapkan dengan modul bersama untuk KMP, meskipun saat ini kosong. Jika project Anda belum disiapkan dengan modul bersama, mulailah dengan codelab Mulai Menggunakan Kotlin Multiplatform.

4. Menyiapkan database Room untuk integrasi KMP

Sebelum memindahkan kode database Room dari aplikasi Android Fruitties ke modul shared, Anda harus memastikan bahwa aplikasi tersebut kompatibel dengan Kotlin Multiplatform (KMP) Room API. Bagian ini akan memandu Anda menjalani proses tersebut.

Salah satu pembaruan penting adalah menggunakan driver SQLite yang kompatibel dengan Android dan iOS. Untuk mendukung fungsi database Room di beberapa platform, Anda dapat menggunakan BundledSQLiteDriver. Driver ini menggabungkan SQLite langsung ke dalam aplikasi, sehingga cocok untuk penggunaan multiplatform di Kotlin. Untuk mendapatkan panduan mendetail, lihat panduan migrasi Room KMP.

Memperbarui dependensi

Pertama, tambahkan dependensi room-runtime dan sqlite-bundled ke file 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" }

Selanjutnya, perbarui build.gradle.kts modul :androidApp untuk menggunakan dependensi ini, dan hapus penggunaan libs.androidx.room.ktx:

// Add
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
// Remove
implementation(libs.androidx.room.ktx)

Sekarang, sinkronkan project di Android Studio.

Mengubah modul database untuk BundledSQLiteDriver

Selanjutnya, ubah logika pembuatan database di aplikasi Android untuk menggunakan BundledSQLiteDriver, sehingga kompatibel dengan KMP sekaligus mempertahankan fungsi di Android.

  1. Buka file DatabaseModule.kt yang berada di androidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt
  2. Perbarui metode providesAppDatabase seperti dalam cuplikan berikut:
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()
}

Membangun dan menjalankan aplikasi Android

Setelah Anda mengalihkan driver SQLite Native ke driver yang digabungkan, verifikasi build aplikasi dan pastikan semuanya berfungsi dengan benar sebelum Anda memigrasikan database ke modul :shared.

5. Memindahkan kode database ke modul :shared

Pada langkah ini, kita akan mentransfer penyiapan database Room dari aplikasi Android ke modul :shared, sehingga database dapat diakses oleh Android dan iOS.

Memperbarui konfigurasi build.gradle.kts dari modul :shared

Mulai dengan memperbarui build.gradle.kts modul :shared untuk menggunakan dependensi multiplatform Room.

  1. Tambahkan plugin KSP dan Room:
plugins {
   ...
   // TODO add KSP + ROOM plugins
   alias(libs.plugins.ksp)
   alias(libs.plugins.room)
}
  1. Tambahkan dependensi room-runtime dan sqlite-bundled ke blok commonMain:
sourceSets {
    commonMain {
        // TODO Add KMP dependencies here
        implementation(libs.androidx.room.runtime)
        implementation(libs.androidx.sqlite.bundled)
    }
}
  1. Tambahkan konfigurasi KSP untuk setiap target platform dengan menambahkan blok dependencies level teratas baru. Untuk memudahkan, Anda cukup menambahkannya ke bagian bawah file:
// 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. Di tingkat teratas, tambahkan juga blok baru untuk menetapkan lokasi skema Room:
// Should be its own top level block. For convenience, add at the bottom of the file
room {
   schemaDirectory("$projectDir/schemas")
}
  1. Lakukan sinkronisasi Gradle pada project

Memindahkan skema Room ke modul :shared

Pindahkan direktori androidApp/schemas ke folder root modul :shared di samping folder src/:

Dari: e1ee37a3f3a10b35.png

Ke: ba3c9eb617828bac.png

Memindahkan DAO dan entity

Setelah menambahkan dependensi Gradle yang diperlukan ke modul bersama KMP, Anda perlu memindahkan DAO dan entity dari modul :androidApp ke modul :shared.

Tindakan ini akan melibatkan pemindahan file ke lokasi masing-masing di set sumber commonMain dalam modul :shared.

Memindahkan model Fruittie

Anda dapat memanfaatkan fungsi Refactor → Move untuk beralih modul tanpa merusak impor:

  1. Temukan file androidApp/src/main/kotlin/.../model/Fruittie.kt, klik kanan file, lalu pilih Refactor → Move (atau tekan tombol F6):c893e12b8bf683ae.png
  2. Pada dialog Move, pilih ikon ... di samping kolom Destination directory. 1d51c3a410e8f2c3.png
  3. Pilih set sumber commonMain di dialog Choose Destination Directory, lalu klik OK. Anda mungkin harus menonaktifkan kotak centang Show only existing source roots. f61561feb28a6445.png
  4. Klik tombol Refactor untuk memindahkan file.

Memindahkan model CartItem dan CartItemWithFruittie

Untuk file androidApp/.../model/CartItem.kt, Anda perlu mengikuti langkah-langkah berikut:

  1. Buka file, klik kanan class CartItem, lalu pilih Refactor > Move.
  2. Tindakan ini akan membuka dialog Move yang sama, tetapi dalam hal ini Anda juga mencentang kotak untuk anggota CartItemWithFruittie.
  3. a25022cce5cee5e0.png Lanjutkan dengan memilih ikon ... dan memilih set sumber commonMain seperti yang Anda lakukan untuk file Fruittie.kt.

Memindahkan DAO dan AppDatabase

Lakukan langkah yang sama untuk file berikut (Anda dapat memilih ketiga file secara bersamaan):

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

Memperbarui AppDatabase bersama agar berfungsi di berbagai platform

Setelah memindahkan class database ke modul :shared, Anda perlu menyesuaikannya untuk menghasilkan implementasi yang diperlukan di kedua platform.

  1. Buka file /shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt.
  2. Tambahkan penerapan RoomDatabaseConstructor berikut:
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. Anotasikan class AppDatabase dengan @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

Memindahkan pembuatan database ke modul :shared

Selanjutnya, Anda akan memindahkan penyiapan Room khusus Android dari modul :androidApp ke modul :shared. Tindakan ini diperlukan karena pada langkah berikutnya, Anda akan menghapus dependensi Room dari modul :androidApp.

  1. Cari file androidApp/.../di/DatabaseModule.kt.
  2. Pilih konten fungsi providesAppDatabase, klik kanan, lalu pilih Refactor > Extract Function to Scope: da4d97319f9a0e8c.png
  3. Pilih DatabaseModule.kt dari menu. 5e540a1eec6e3493.png Tindakan ini akan memindahkan konten ke fungsi appDatabase global. Tekan Enter untuk mengonfirmasi nama fungsi. e2fb113d66704a36.png
  4. Buat fungsi menjadi publik dengan menghapus pengubah visibilitas private.
  5. Pindahkan fungsi ke modul :shared dengan mengklik kanan Refactor > Move.
  6. Pada dialog Move, pilih ikon ... di samping kolom Destination directory. e2101005f2ef4747.png
  7. Pada dialog Choose Destination Directory, pilih set sumber shared >androidMain dan pilih folder /shared/src/androidMain/, lalu klik OK.73d244941c68dc85.png
  8. Ubah akhiran di kolom To package dari .di menjadi .databaseac5cf30d32871e2c.png
  9. Klik Refactor.

Membersihkan kode yang tidak diperlukan dari :androidApp

Pada tahap ini, Anda telah memindahkan database Room ke modul multiplatform dan tidak ada dependensi Room yang diperlukan dalam modul :androidApp, sehingga Anda dapat menghapusnya.

  1. Buka file build.gradle.kts di modul :androidApp.
  2. Hapus dependensi dan konfigurasi seperti dalam cuplikan berikut:
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. Lakukan sinkronisasi Gradle pada project.

Membangun dan menjalankan aplikasi Android

Jalankan aplikasi Android Fruitties untuk memastikan aplikasi berjalan dengan baik dan sekarang menggunakan database dari modul :shared. Jika sebelumnya Anda telah menambahkan item keranjang, Anda juga akan melihat item yang sama pada tahap ini meskipun database Room kini berada di modul :shared.

6. Menyiapkan Room untuk digunakan di iOS

Guna menyiapkan database Room untuk platform iOS, Anda perlu menyiapkan beberapa kode pendukung di modul :shared untuk digunakan di langkah berikutnya.

Mengaktifkan pembuatan database untuk aplikasi iOS

Hal pertama yang harus dilakukan adalah menambahkan builder database khusus iOS.

  1. Tambahkan file baru di modul :shared dalam set sumber iosMain bernama AppDatabase.ios.kt: dcb46ba560298865.png
  2. Tambahkan fungsi bantuan berikut. Fungsi ini akan digunakan oleh aplikasi iOS untuk mendapatkan instance database 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")
        }
    }
}

Menambahkan akhiran "Entity" ke entity Room

Karena Anda menambahkan wrapper untuk entity Room di Swift, sebaiknya gunakan nama entity Room yang berbeda dengan nama wrapper. Pastikan hal tersebut dengan menambahkan akhiran Entity ke entity Room menggunakan anotasi @ObjCName.

Buka file Fruittie.kt di modul :shared dan tambahkan anotasi @ObjCName ke entity Fruittie. Karena anotasi ini bersifat eksperimental, Anda mungkin perlu menambahkan anotasi @OptIn(ExperimentalObjC::class) ke file.

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(
   ...
)

Kemudian, lakukan hal yang sama untuk entity CartItem dalam file CartItem.kt.

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. Menggunakan Room di aplikasi iOS

Aplikasi iOS adalah aplikasi yang sudah ada yang menggunakan Core Data. Dalam codelab ini, Anda tidak perlu khawatir tentang memigrasikan data yang ada di database karena aplikasi ini hanyalah prototipe. Jika memigrasikan aplikasi produksi ke KMP, Anda harus menulis fungsi untuk membaca database Core Data saat ini dan menyisipkan item tersebut ke database Room saat peluncuran pertama setelah migrasi.

Membuka project Xcode

Buka project iOS di Xcode dengan membuka folder /iosApp/ dan membuka Fruitties.xcodeproj di aplikasi terkait.

54836291a243ebe9.png

Menghapus class entity Core Data

Pertama, Anda harus menghapus class entity Core Data untuk memberi ruang bagi wrapper entity yang akan Anda buat nanti. Bergantung pada jenis data yang disimpan aplikasi Anda di Core Data, Anda dapat menghapus entity Core Data sepenuhnya, atau menyimpannya untuk tujuan migrasi data. Untuk tutorial ini, Anda dapat menghapusnya, karena Anda tidak perlu memigrasikan data yang ada.

Di Xcode:

  1. Buka Project Navigator.
  2. Buka folder Resources.
  3. Buka file Fruitties.
  4. Klik dan hapus setiap entity.

7ad742d991d76b1c.png

Agar perubahan ini tersedia dalam kode, bersihkan dan buat ulang project.

Tindakan ini akan menggagalkan build dengan error berikut, yang diharapkan terjadi.

e3e107bf0387eeab.png

Membuat wrapper entity

Selanjutnya, kita akan membuat wrapper entity untuk entity Fruittie dan CartItem guna menangani perbedaan API antara entity Room dan Core Data dengan lancar.

Wrapper ini akan membantu proses transisi dari Core Data ke Room dengan meminimalkan jumlah kode yang harus segera diperbarui. Anda harus berupaya mengganti wrapper ini dengan akses langsung ke entity Room pada masa mendatang.

Untuk saat ini, kita akan membuat wrapper untuk class FruittieEntity, yang akan memberikan properti opsional.

Membuat wrapper FruittieEntity

  1. Buat file Swift baru di direktori Sources/Repository dengan mengklik kanan nama direktori dan memilih New File from Template... cce140b2fb3c2da8.png 6a0d4fa4292ddd4f.png
  2. Beri nama Fruittie dan pastikan hanya target Fruitties yang dipilih, bukan target pengujian. 827b9019b0a32352.png
  3. Tambahkan kode berikut ke file Fruittie baru:
import sharedKit

struct Fruittie: Hashable {
   let entity: FruittieEntity

   var id: Int64 {
       entity.id
   }

   var name: String? {
       entity.name
   }

   var fullName: String? {
       entity.fullName
   }
}

Struct Fruittie menggabungkan class FruittieEntity, sehingga properti menjadi opsional dan meneruskan properti entity. Selain itu, kita akan membuat struct Fruittie sesuai dengan protokol Hashable, sehingga dapat digunakan di tampilan ForEach SwiftUI.

Membuat wrapper CartItemEntity

Selanjutnya, buat wrapper serupa untuk class CartItemEntity.

Buat file Swift baru bernama CartItem.swift di direktori Sources/Repository.

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)
   }
}

Karena class CartItem Core Data asli memiliki properti Fruittie, kami juga telah menyertakan properti Fruittie dalam struct CartItem. Meskipun class CartItem tidak memiliki properti yang bersifat opsional, properti count memiliki jenis yang berbeda di entity Room.

Memperbarui repositori

Setelah wrapper entity diterapkan, Anda perlu memperbarui DefaultCartRepository dan DefaultFruittieRepository untuk menggunakan Room, bukan Core Data.

Memperbarui DefaultCartRepository

Mari kita mulai dengan class DefaultCartRepository karena lebih sederhana dari keduanya.

Buka file CartRepository.swift di direktori Sources/Repository.

  1. Pertama, ganti impor CoreData dengan sharedKit:
import sharedKit
  1. Kemudian, hapus properti NSManagedObjectContext dan ganti dengan properti CartDao:
// Remove
private let managedObjectContext: NSManagedObjectContext

// Replace with
private let cartDao: any CartDao
  1. Perbarui konstruktor init untuk melakukan inisialisasi properti cartDao baru:
init(cartDao: any CartDao) {
    self.cartDao = cartDao
}
  1. Selanjutnya, perbarui metode addToCart. Metode ini diperlukan untuk mengambil item keranjang yang ada dari Core Data, tetapi implementasi Room kita tidak memerlukannya. Sebagai gantinya, item baru akan disisipkan atau jumlah item keranjang yang ada akan bertambah.
func addToCart(fruittie: Fruittie) async throws {
    try await cartDao.insertOrIncreaseCount(fruittie: fruittie.entity)
}
  1. Terakhir, perbarui metode getCartItems(). Metode ini akan memanggil metode getAll() di CartDao dan memetakan entity CartItemWithFruittie ke wrapper CartItem.
func getCartItems() -> AsyncStream<[CartItem]> {
    return cartDao.getAll().map { entities in
        entities.map(CartItem.init(entity:))
    }.eraseToStream()
}

Memperbarui DefaultFruittieRepository

Untuk memigrasikan class DefaultFruittieRepository, terapkan perubahan serupa seperti yang dilakukan untuk DefaultCartRepository.

Perbarui file FruittieRepository dengan perubahan berikut:

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()
    }
}

Mengganti wrapper properti @FetchRequest

Kita juga perlu mengganti wrapper properti @FetchRequest di tampilan SwiftUI. Wrapper properti @FetchRequest digunakan untuk mengambil data dari Core Data dan mengamati perubahan, sehingga kita tidak dapat menggunakannya dengan entity Room. Sebagai gantinya, gunakan UIModel untuk mengakses data dari repositori.

  1. Buka CartView di file Sources/UI/CartView.swift.
  2. Ganti implementasi dengan yang berikut:
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)")
                    }
                }
            }
        }
    }
}

Memperbarui ContentView

Perbarui ContentView dalam file Sources/View/ContentView.swift untuk meneruskan FruittieUIModel ke CartView.

CartView(uiModel: uiModel)

Memperbarui DataController

Class DataController di aplikasi iOS bertanggung jawab untuk menyiapkan stack Core Data. Karena kita beralih dari Core Data, kita perlu memperbarui DataController untuk melakukan inisialisasi pada database Room.

  1. Buka file DataController.swift di Sources/Database.
  2. Tambahkan impor sharedKit.
  3. Hapus impor CoreData.
  4. Buat instance database Room di class DataController.
  5. Terakhir, hapus panggilan metode loadPersistentStores dari penginisialisasi DataController.

Class akhir akan terlihat seperti ini:

import Combine
import sharedKit

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

Memperbarui injeksi dependensi

Class AppContainer di aplikasi iOS bertanggung jawab untuk melakukan inisialisasi pada grafik dependensi. Karena kita memperbarui repositori untuk menggunakan Room, bukan Core Data, kita perlu memperbarui AppContainer untuk meneruskan DAO Room ke repositori.

  1. Buka AppContainer.swift di folder Sources/DI.
  2. Tambahkan impor sharedKit.
  3. Hapus properti managedObjectContext dari class AppContainer.
  4. Ubah inisialisasi DefaultFruittieRepository dan DefaultCartRepository dengan meneruskan DAO Room dari instance AppDatabase yang disediakan oleh DataController.

Setelah selesai, class akan terlihat seperti ini:

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()
        )
    }
}

Terakhir, perbarui FruittiesApp di main.swift untuk menghapus managedObjectContext:

struct FruittiesApp: App {
    @StateObject
    private var appContainer = AppContainer()

    var body: some Scene {
        WindowGroup {
            ContentView(appContainer: appContainer)
        }
    }
}

Membangun dan menjalankan aplikasi iOS

Terakhir, setelah Anda membangun dan menjalankan aplikasi dengan menekan ⌘R, aplikasi akan dimulai dengan database yang dimigrasikan dari Core Data ke Room.

5d2ae9438747f8f6.png

8. Selamat

Selamat! Anda telah berhasil memigrasikan aplikasi Android dan iOS mandiri ke lapisan data bersama menggunakan Room KMP.

Sebagai referensi, berikut adalah perbandingan arsitektur aplikasi untuk melihat hasil yang dicapai:

Sebelum

Android

iOS

Diagram arsitektur aplikasi Android sebelum integrasi KMP

Diagram arsitektur aplikasi iOS sebelum integrasi KMP

Arsitektur Pasca Migrasi

bcd8c29b00f67c19.png

Pelajari lebih lanjut