1. 시작하기 전에
기본 요건
- Kotlin 멀티플랫폼에 관한 기본적 이해
- Kotlin 사용 경험
- Swift 문법에 관한 기본 이해
- Xcode 및 iOS 시뮬레이터 설치
필요한 항목
- 최신 안정화 버전의 Android 스튜디오
- 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 스튜디오에서 다음 브랜치가 포함된
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 앱은 다음과 같은 두 가지 주요 기능을 제공합니다.
- 장바구니에 상품을 추가하는 버튼이 있는 과일 상품 목록입니다.
- 상단에 추가된 과일의 수와 수량을 보여주는 장바구니가 표시됩니다.
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 이전 가이드를 참고하세요.
종속 항목 업데이트
먼저 libs.versions.toml
파일에 room-runtime
및 sqlite-bundled
종속 항목을 추가합니다.
# 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 스튜디오에서 프로젝트를 동기화합니다.
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)
}
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 및 항목 이동
이제 KMP 공유 모듈에 필요한 Gradle 종속 항목을 추가했으므로 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
이동
다음 파일에도 동일한 단계를 수행합니다 (세 파일을 모두 동시에 선택할 수 있음).
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
공개 상태 수정자를 삭제하여 함수를 공개로 설정합니다.- Refactor > Move를 마우스 오른쪽 버튼으로 클릭하여 함수를
:shared
모듈로 이동합니다. - 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에서:
- 프로젝트 탐색기로 이동합니다.
- Resources 폴더로 이동합니다.
Fruitties
파일을 엽니다.- 각 항목을 클릭하여 삭제합니다.
이러한 변경사항을 코드에서 사용할 수 있도록 하려면 프로젝트를 정리하고 다시 빌드합니다.
그러면 다음과 같은 오류가 발생하여 빌드가 실패합니다. 이는 예상된 동작입니다.
항목 래퍼 만들기
다음으로 Room과 Core Data 항목 간의 API 차이를 원활하게 처리하기 위해 Fruittie
및 CartItem
항목의 항목 래퍼를 만듭니다.
이러한 래퍼는 즉시 업데이트해야 하는 코드의 양을 최소화하여 Core Data에서 Room으로 전환하는 데 도움이 됩니다. 향후 이러한 래퍼를 Room 항목에 대한 직접 액세스로 대체하는 것이 좋습니다.
지금은 대신 FruittieEntity
클래스의 래퍼를 만들어 선택적 속성을 지정합니다.
FruittieEntity
래퍼 만들기
- 디렉터리 이름을 마우스 오른쪽 버튼으로 클릭하고 New File from Template...을 선택하여
Sources/Repository
디렉터리에 새 Swift 파일을 만듭니다. - 이름을
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
클래스에는 선택사항인 속성이 없지만 count
속성은 Room 항목에서 다른 유형을 갖습니다.
저장소 업데이트
이제 항목 래퍼가 있으므로 Core Data 대신 Room을 사용하도록 DefaultCartRepository
및 DefaultFruittieRepository
를 업데이트해야 합니다.
DefaultCartRepository
업데이트
두 클래스 중 더 간단한 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()
)
}
}
마지막으로 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 멀티플랫폼 문서를 확인하세요.