1. Antes de começar
Pré-requisitos
- Noções básicas do Kotlin Multiplatform.
- Experiência com Kotlin.
- Conhecimentos básicos sobre a sintaxe do Swift.
- Xcode e simulador iOS instalados.
O que você precisa
- A versão estável mais recente do Android Studio.
- Uma máquina Mac com um sistema macOS.
- Xcode 16.1 e simulador do iPhone com iOS 16.0 ou mais recente.
O que você vai aprender
- Como compartilhar um banco de dados do Room entre um app Android e um app iOS.
2. Começar a configuração
Para começar, siga estas etapas:
- Clone o repositório do GitHub com o seguinte comando do terminal:
$ git clone https://github.com/android/codelab-android-kmp.git
Se preferir, faça o download do repositório como um arquivo ZIP:
- No Android Studio, abra o projeto
migrate-room
, que contém as ramificações abaixo:
main
: contém o código inicial do projeto, em que você vai fazer mudanças para concluir o codelab.end
: contém o código da solução do codelab.
É recomendado começar pela ramificação main
e seguir cada etapa do codelab no seu próprio ritmo.
- Para conferir o código da solução, execute este comando:
$ git clone -b end https://github.com/android/codelab-android-kmp.git
Você também pode baixar o código da solução:
3. Entender o app de exemplo
Este tutorial consiste no aplicativo de exemplo Fruitties criado em frameworks nativos (Jetpack Compose no Android, SwiftUi no iOS).
O app Fruitties oferece dois recursos principais:
- Uma lista de itens de frutas, cada um com um botão para adicionar o item a um carrinho.
- Um carrinho que aparece na parte de cima, mostrando quantas frutas foram adicionadas e as quantidades.
Arquitetura do app Android
O app Android segue as diretrizes oficiais de arquitetura do Android para manter uma estrutura clara e modular.
Arquitetura do app iOS
Módulo compartilhado do KMP
Este projeto já foi configurado com um módulo compartilhado para o KMP, embora esteja vazio no momento. Se o projeto ainda não tiver um módulo compartilhado, comece com o codelab Começar a usar o Kotlin Multiplatform.
4. Preparar o banco de dados do Room para a integração do KMP
Antes de mover o código do banco de dados do Room do app Android Fruitties para o módulo shared
, verifique se ele é compatível com as APIs Room para Kotlin Multiplatform (KMP). Esta seção explica esse processo.
Uma das principais atualizações é o uso de um driver SQLite compatível com Android e iOS. Para oferecer suporte à funcionalidade do banco de dados do Room em várias plataformas, use BundledSQLiteDriver
. Esse driver agrupa o SQLite diretamente no aplicativo, tornando-o adequado para uso em várias plataformas no Kotlin. Para receber orientações detalhadas, consulte o guia de migração do Room KMP.
Atualizar dependências
Primeiro, adicione as dependências room-runtime
e sqlite-bundled
ao arquivo 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" }
Em seguida, atualize o build.gradle.kts
do módulo :androidApp
para usar essas dependências e remova o uso de libs.androidx.room.ktx
:
// Add
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
// Remove
implementation(libs.androidx.room.ktx)
Agora, sincronize o projeto no Android Studio.
Modificar o módulo de banco de dados do BundledSQLiteDriver
Em seguida, modifique a lógica de criação de banco de dados no app Android para usar o BundledSQLiteDriver
, tornando-o compatível com o KMP e mantendo a funcionalidade no Android.
- Abra o arquivo
DatabaseModule.kt
localizado emandroidApp/src/main/kotlin/com/example/fruitties/kmptutorial/android/di/DatabaseModule.kt
. - Atualize o método
providesAppDatabase
conforme o snippet abaixo:
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()
}
Criar e executar o app Android
Agora que você mudou o driver SQLite nativo para o agrupado, vamos verificar os builds do app e se tudo funciona corretamente antes de migrar o banco de dados para o módulo :shared
.
5. Mover o código do banco de dados para o módulo :shared
Nesta etapa, vamos transferir a configuração do banco de dados do Room do app Android para o módulo :shared
, permitindo que o banco de dados seja acessível pelo Android e pelo iOS.
Atualizar a configuração build.gradle.kts
do módulo :shared
Comece atualizando o build.gradle.kts
do módulo :shared
para usar dependências multiplataforma do Room.
- Adicione os plug-ins KSP e Room:
plugins {
...
// TODO add KSP + ROOM plugins
alias(libs.plugins.ksp)
alias(libs.plugins.room)
}
- Adicione as dependências
room-runtime
esqlite-bundled
ao blococommonMain
:
sourceSets {
commonMain {
// TODO Add KMP dependencies here
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
}
- Adicione uma nova configuração de KSP para cada plataforma de destino adicionando um novo bloco
dependencies
de nível superior. Para facilitar, adicione-o na parte de baixo do arquivo:
// 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)
}
- Também no nível superior, adicione um novo bloco para definir o local do esquema
Room
:
// Should be its own top level block. For convenience, add at the bottom of the file
room {
schemaDirectory("$projectDir/schemas")
}
- O Gradle sincroniza o projeto
Mover o esquema do Room para o módulo :shared
Mova o diretório androidApp/schemas
para a pasta raiz do módulo :shared
ao lado da pasta src/
:
De:
Para:
Mover DAOs e entidades
Agora que você adicionou as dependências do Gradle necessárias ao módulo compartilhado do KMP, é necessário mover os DAOs e entidades do módulo :androidApp
para o módulo :shared
.
Isso envolve mover arquivos para os respectivos locais no conjunto de origem commonMain
no módulo :shared
.
Mover o modelo Fruittie
Você pode aproveitar a funcionalidade Refactor → Move para mudar entre módulos sem interromper as importações:
- Localize o arquivo
androidApp/src/main/kotlin/.../model/Fruittie.kt
, clique com o botão direito do mouse e selecione Refactor → Move (ou pressione F6): - Na caixa de diálogo Move, selecione o ícone
...
ao lado do campo Destination directory. - Selecione o conjunto de origem commonMain na caixa de diálogo Choose Destination Directory e clique em OK. Talvez seja necessário desativar a caixa de seleção Show only existing source roots.
- Clique no botão Refactor para mover o arquivo.
Mover modelos CartItem
e CartItemWithFruittie
Para o arquivo androidApp/.../model/CartItem.kt
, siga estas etapas:
- Abra o arquivo, clique com o botão direito do mouse na classe
CartItem
e selecione Refactor > Move. - Isso abre a mesma caixa de diálogo Move, mas nesse caso, você também marca a caixa do membro
CartItemWithFruittie
. Continue selecionando o ícone
...
e o conjunto de origemcommonMain
, assim como você fez com o arquivoFruittie.kt
.
Mover DAOs e AppDatabase
Siga as mesmas etapas para os seguintes arquivos (você pode selecionar os três ao mesmo tempo):
androidApp/.../database/FruittieDao.kt
androidApp/.../database/CartDao.kt
androidApp/.../database/AppDatabase.kt
Atualizar o AppDatabase
compartilhado para funcionar em várias plataformas
Agora que você moveu as classes do banco de dados para o módulo :shared
, é necessário ajustá-las para gerar as implementações necessárias nas duas plataformas.
- Abra o arquivo
/shared/src/commonMain/kotlin/com/example/fruitties/kmptutorial/android/database/AppDatabase.kt
. - Adicione a seguinte implementação de
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
}
- Adicione a anotação
@ConstructedBy(AppDatabaseConstructor::class)
à classeAppDatabase
:
import androidx.room.ConstructedBy
@Database(
entities = [Fruittie::class, CartItem::class],
version = 1,
)
// TODO Add this line
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
...
Mover a criação do banco de dados para o módulo :shared
Em seguida, você move a configuração do Room específica do Android do módulo :androidApp
para o módulo :shared
. Isso é necessário porque na próxima etapa você vai remover a dependência Room
do módulo :androidApp
.
- Localize o arquivo
androidApp/.../di/DatabaseModule.kt
. - Selecione o conteúdo da função
providesAppDatabase
, clique com o botão direito do mouse e selecione Refactor > Extract Function to Scope: - Selecione
DatabaseModule.kt
no menu.Isso move o conteúdo para uma função
appDatabase
global. Pressione Enter para confirmar o nome da função. - Remova o modificador de visibilidade
private
para tornar a função pública. - Mova a função para o módulo
:shared
clicando com o botão direito do mouse em Refactor > Move. - Na caixa de diálogo Move, selecione o ícone ... ao lado do campo Destination directory.
- Na caixa de diálogo Choose Destination Directory, selecione o conjunto de origem shared >androidMain e a pasta /shared/src/androidMain/. Em seguida, clique em OK.
- Mude o sufixo no campo To package de
.di
para.database
- Clique em Refactor.
Limpeza do código desnecessário do :androidApp
Nesse ponto, você moveu o banco de dados do Room para o módulo multiplataforma, e nenhuma das dependências do Room é necessária no módulo :androidApp
. Portanto, é possível removê-las.
- Abra o arquivo
build.gradle.kts
no módulo:androidApp
. - Remova as dependências e a configuração, conforme mostrado no snippet abaixo:
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")
}
- O Gradle sincroniza o projeto.
Criar e executar o app Android
Execute o app Android Fruitties e confira se ele funciona corretamente e usa o banco de dados do módulo :shared
. Se você tiver adicionado itens ao carrinho antes, eles também vão aparecer neste ponto, mesmo que o banco de dados do Room esteja localizado no módulo :shared
.
6. Preparar o Room para uso no iOS
Para preparar ainda mais o banco de dados do Room para a plataforma iOS, você precisa configurar um código de suporte no módulo :shared
para uso na próxima etapa.
Ativar a criação de banco de dados para o app iOS
A primeira coisa a fazer é adicionar um criador de banco de dados específico para iOS.
- Adicione um novo arquivo ao módulo
:shared
no conjunto de origemiosMain
com o nomeAppDatabase.ios.kt
: - Adicione as seguintes funções auxiliares. Essas funções serão usadas pelo app iOS para receber uma instância do banco de dados do 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")
}
}
}
Adicionar o sufixo "Entity" às entidades do Room
Como você está adicionando wrappers para as entidades do Room no Swift, é melhor que os nomes das entidades do Room sejam diferentes dos nomes dos wrappers. Para garantir que esse seja o caso, adicione o sufixo Entity às entidades do Room usando a anotação @ObjCName
.
Abra o arquivo Fruittie.kt
no módulo :shared
e adicione a anotação @ObjCName
à entidade Fruittie
. Como essa anotação é experimental, talvez seja necessário adicionar a anotação @OptIn(ExperimentalObjC::class)
ao arquivo.
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(
...
)
Em seguida, faça o mesmo para a entidade CartItem
no arquivo 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. Usar o Room no app iOS
O app iOS é um app já pronto que usa Core Data. Neste codelab, você não precisa se preocupar em migrar dados do banco de dados, porque este app é apenas um protótipo. Se você estiver migrando um app de produção para o KMP, vai precisar escrever funções para ler o banco de dados atual do Core Data e inserir esses itens no banco de dados do Room na primeira inicialização após a migração.
Abrir o projeto do Xcode
Abra o projeto iOS no Xcode navegando até a pasta /iosApp/
e abrindo Fruitties.xcodeproj
no app associado.
Remover classes de entidade do Core Data
Primeiro, você precisa remover as classes de entidade do Core Data para abrir espaço para os wrappers de entidade que serão criados mais tarde. Dependendo do tipo de dados armazenados pelo app no Core Data, é possível remover as entidades do Core Data por completo ou mantê-las para fins de migração de dados. Para este tutorial, basta removê-los, já que não é necessário migrar os dados atuais.
No Xcode:
- Acesse o Project Navigator.
- Acesse a pasta "Resources".
- Abra o arquivo
Fruitties
. - Clique e exclua cada entidade.
Para disponibilizar essas mudanças no código, limpe e recrie o projeto.
Isso vai fazer com que o build falhe com os seguintes erros, o que é esperado.
Como criar wrappers de entidade
Em seguida, vamos criar wrappers de entidade para as entidades Fruittie
e CartItem
para processar facilmente as diferenças de API entre as entidades do Room e do Core Data.
Esses wrappers vão nos ajudar a fazer a transição do Core Data para o Room, minimizando a quantidade de código que precisa ser atualizado imediatamente. No futuro, tente substituir esses wrappers pelo acesso direto às entidades do Room.
Por enquanto, vamos criar um wrapper para a classe FruittieEntity
, atribuindo propriedades opcionais a ela.
Criar um wrapper de FruittieEntity
- Crie um novo arquivo Swift no diretório
Sources/Repository
clicando com o botão direito do mouse no nome do diretório e selecionando New File from Template… - Nomeie-o como
Fruittie
e confira se apenas o destino "Fruitties" está selecionado, e não o destino de teste. - Adicione o código abaixo ao novo arquivo Fruittie:
import sharedKit
struct Fruittie: Hashable {
let entity: FruittieEntity
var id: Int64 {
entity.id
}
var name: String? {
entity.name
}
var fullName: String? {
entity.fullName
}
}
A estrutura Fruittie
envolve a classe FruittieEntity
, tornando as propriedades opcionais e passando pelas propriedades da entidade. Além disso, fazemos com que o struct Fruittie
obedeça ao protocolo Hashable
para que ele possa ser usado na visualização ForEach
da SwiftUI.
Criar um wrapper de CartItemEntity
Em seguida, crie um wrapper semelhante para a classe CartItemEntity
.
Crie um novo arquivo Swift chamado CartItem.swift
no diretório 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)
}
}
Como a classe CartItem
original do Core Data tinha uma propriedade Fruittie
, também incluímos uma propriedade Fruittie
na estrutura CartItem
. Embora a classe CartItem
não tenha propriedades opcionais, a propriedade count
tem um tipo diferente na entidade do Room.
Atualizar repositórios
Agora que os wrappers de entidade estão em vigor, é necessário atualizar o DefaultCartRepository
e o DefaultFruittieRepository
para usar o Room em vez do Core Data.
Atualizar DefaultCartRepository
Vamos começar com a classe DefaultCartRepository
, que é mais simples.
Abra o arquivo CartRepository.swift
no diretório Sources/Repository
.
- Primeiro, substitua a importação
CoreData
porsharedKit
:
import sharedKit
- Em seguida, remova a propriedade
NSManagedObjectContext
e substitua-a por uma propriedadeCartDao
:
// Remove
private let managedObjectContext: NSManagedObjectContext
// Replace with
private let cartDao: any CartDao
- Atualize o construtor
init
para inicializar a nova propriedadecartDao
:
init(cartDao: any CartDao) {
self.cartDao = cartDao
}
- Em seguida, atualize o método
addToCart
. Esse método precisava extrair os itens do carrinho do Core Data, mas nossa implementação do Room não exige isso. Em vez disso, ele insere um novo item ou incrementa a contagem de um item do carrinho.
func addToCart(fruittie: Fruittie) async throws {
try await cartDao.insertOrIncreaseCount(fruittie: fruittie.entity)
}
- Por fim, atualize o método
getCartItems()
. Esse método vai chamar o métodogetAll()
noCartDao
e mapear as entidadesCartItemWithFruittie
para nossos wrappersCartItem
.
func getCartItems() -> AsyncStream<[CartItem]> {
return cartDao.getAll().map { entities in
entities.map(CartItem.init(entity:))
}.eraseToStream()
}
Atualizar DefaultFruittieRepository
Para migrar a classe DefaultFruittieRepository
, aplicamos mudanças semelhantes às que fizemos para o DefaultCartRepository
.
Atualize o arquivo FruittieRepository para o seguinte:
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()
}
}
Substituir wrappers de propriedade @FetchRequest
Também precisamos substituir os wrappers de propriedade @FetchRequest
nas visualizações da SwiftUI. O wrapper de propriedade @FetchRequest
é usado para buscar dados do Core Data e observar mudanças. Portanto, não é possível usá-lo com entidades do Room. Em vez disso, vamos usar o UIModel para acessar os dados dos repositórios.
- Abra o
CartView
no arquivoSources/UI/CartView.swift
. - Substitua a implementação pelo seguinte:
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)")
}
}
}
}
}
}
Atualizar a ContentView
Atualize a ContentView
no arquivo Sources/View/ContentView.swift
para transmitir o FruittieUIModel
à CartView
.
CartView(uiModel: uiModel)
Como atualizar o DataController
A classe DataController
no aplicativo iOS é responsável por configurar a pilha do Core Data. Como estamos nos afastando do Core Data, precisamos atualizar o DataController
para inicializar o banco de dados do Room.
- Abra o arquivo
DataController.swift
emSources/Database
. - Adicione a importação
sharedKit
. - Remova a importação
CoreData
. - Crie uma instância do banco de dados do Room na classe
DataController
. - Por fim, remova a chamada de método
loadPersistentStores
do inicializadorDataController
.
A classe atualizada ficará assim:
import Combine
import sharedKit
class DataController: ObservableObject {
let database = getPersistentDatabase()
init() {}
}
Como atualizar a injeção de dependência
A classe AppContainer
no aplicativo iOS é responsável por inicializar o gráfico de dependências. Como atualizamos os repositórios para usar o Room em vez do Core Data, precisamos atualizar o AppContainer
para transmitir os DAOs do Room aos repositórios.
- Abra o
AppContainer.swift
na pastaSources/DI
. - Adicione a importação
sharedKit
. - Remova a propriedade
managedObjectContext
da classeAppContainer
. - Mude as inicializações de
DefaultFruittieRepository
eDefaultCartRepository
transmitindo DAOs do Room da instânciaAppDatabase
fornecida peloDataController
.
Quando terminar, a classe vai ficar assim:
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()
)
}
}
Por fim, atualize FruittiesApp
em main.swift
para remover o managedObjectContext
:
struct FruittiesApp: App {
@StateObject
private var appContainer = AppContainer()
var body: some Scene {
WindowGroup {
ContentView(appContainer: appContainer)
}
}
}
Criar e executar o app iOS
Por fim, depois de criar e executar o app pressionando ⌘R, ele vai começar com o banco de dados migrado do Core Data para o Room.
8. Parabéns
Parabéns! Você migrou apps Android e iOS independentes para uma camada de dados compartilhada usando o KMP do Room.
Para referência, confira a comparação das arquiteturas de apps para ver o que fizemos:
Antes
Android | iOS |
Após a migração para Android
Saiba mais (links em inglês)
- Saiba quais outras bibliotecas do Jetpack têm suporte ao KMP.
- Leia a documentação do Room KMP.
- Leia a documentação do SQLite KMP.
- Consulte a documentação oficial do Kotlin Multiplatform.