Setup Banco de dados SqlDelight em KMM
Aprenda como configurar e compartilhar o banco de dados SqlDelight em seu projeto kotlin multiplatform mobile KMM
Classes envolvidas
Primeiro vamos isolar as classes e arquivos envolvidads no processo de criação para facilitar o entendimento.
AppApplication (android app): Responsável por injetar o contexto da aplicação no androidMain.
AndroidApplication (androidMain): Responsável em fornecer o contexto da aplicação na criação do banco de dados SqlDelight
expect DataBaseDriverFactory (commonMain/Shared): Interface comum que será usada por ambas as plataformas (iOS e Android
actual DataBaseDriverFactory (iOSMain/androidMain): Implementação concreta de cada plataforma (iOS e Android)
Database: Responsável em criar o banco de dados usando DataBaseDriverFactory
AppDatabase.sq: Arquivo responsável por definir a criação da tabela do banco e os comandos SQL que forneceremos.
Peculiaridade Android
Antes de detalharmos cada classe, logo percebemos que a plataforma Android tem algo peculiar, conforme vemos na imagem 2 abaixo. Essa implementação foi derivada de um kotlin handson, onde o banco de dados foi implementado usando ainda o Xml (old school) e passando o contexto dentro das activities. A implementação a seguir já considera Compose. (veja vídeo passo a passo no final desse post)
ApplicationContext
A API da biblioteca SqlDelight requer o contexto da aplicação para poder funcionar corretamente, na teoria, poderiamos passar esse contexto pelo construtor da classe Database. (Como descrito na Imagem 2 acima)
No entanto, como queremos compartilhar os ViewModels (veja o post Compartilhar ViewModel KMM) e não ter que ficar passando o contexto em todos os ViewModels, Repositórios ou DataSourceProviders que venhamos a implementar, optamos por injetar o applictionContext ao iniciar o app.
Dessa forma, podemos usar o banco de dados em qualquer lugar, sem nos preocupar com essa dependência.
Não vai vazar memória?
Importante notar que estamos injetando o ApplicationContext e não o contexto em si, assim nos asseguramos que não haverá vazamento de memória.
Neste vídeo: Targeting Android in a Kotlin/Multiplatform Mobile library da equipe de Kodein Koders explica isso melhor no minuto 5:35, sendo uma das formas de acessar o ApplicationContext em Android.
Desta forma, podemos usar o banco de dados em qualquer lugar sem nos preocuparmos com essa dependência.
Mas e dependência cíclica ou null pointer exception?
Isso também não acontece, pois essa dependência só será injetada quando o aplicativo for iniciado. Como o Application é a primeira classe que é criada em todas as aplicações Android, ao ser criada, nos certificamos que essa classe injete seu ApplicationContext no código compartilhado.
AppAplication injeta seu contexto no código comum. (vide imagem 3)
Implementação referência
Como seria a implementação das Classe DataBaseDriverFactory? Vejamos os code snippets a seguir. Iremos fazer uso do mecanismo de expect/actual disponível em KMM:
commonMain (Shared)
Nó código compartilhado precisamos definir a interface comum usando a palavra reservada expect e o arquivo que especifica as tabelas do banco de dados, como também os métodos que iremos oferecer.
ATENÇÃO: não se preocupe com código fonte agora, no final desse post, você encontrará um vídeo, que explica passo-a-passo como criar a estrutura descrita e como acessar o código no github. Os code snippets a seguir sevem apenas para ilustrar como seria fácil implementar os componentes envolvidos.
DatabaseDriverFactory
import com.squareup.sqldelight.db.SqlDriver
expect fun createSqlDriver(): SqlDriver
AppDatabase.sq
CREATE TABLE Story(
id TEXT NOT NULL,
body TEXT NOT NULL
);
insertStory:
INSERT INTO Story (id, body) VALUES(?,?);
removeAllStory:
DELETE FROM Story;
selectStoryById:
SELECT * FROM Story WHERE id = ?;
selectAllStories:
SELECT * FROM Story;
iOSMain
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
actual fun createSqlDriver(): SqlDriver {
return NativeSqliteDriver(CommonDatabase.Schema, "common.db")
}
androidMain
import br.com.progdeelite.utils.AndroidApplication
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.db.SqlDriver
actual fun createSqlDriver(): SqlDriver {
return AndroidSqliteDriver(CommonDatabase.Schema, AndroidApplication.context, "common.db")
}
Banco de dados
Criamos a classe de banco de dados (Database) que faz uso da implementação através da interface comum. Nela injetamos o driver que criamos.
Database
class Database(driver: SqlDriver, clearDatabase: Boolean = false) {
private val database = CommonDatabase(driver)
private val dbQuery = database.appDatabaseQueries
init {
if (clearDatabase) {
clearDatabase()
}
}
fun clearDatabase() {
dbQuery.transaction {
// nome que especificamos no schema
dbQuery.removeAllStory()
}
}
// ... code omited ...
}
Provedor de Recursos
Por fim fazemos uso da classe Database em nosso XyzDataSourceProvider. Nele exergamos o benefício de ter injetado todas as dependências já na criação do Sql driver, como podemos ver na chamada createSqlDriver() que foi implementado em cada plataforma. Nota: Xyz representa o prefixo da implementação concreta.
XyzDataSourceProvider
import br.com.progdeelite.kmmprogdeelite.database.Database
import br.com.progdeelite.kmmprogdeelite.database.createSqlDriver
class XyzDataSourceProvider {
private val database = Database(createSqlDriver())
fun getLocalCommonDatabase() = database
// Outros provedores de dados que voce poderia ter aqui mais pra frente
// fun getXyzRepository()
// fun getXyzUseCase()
}
Você precisa compartilhar o banco de dados local em um projeto KMM com Compose?
Veja na prática como se faz!
Agora que você já sabe como esboçar e visualizar a definição e criação de um banco de dados compartilhado, veja como criar o código necessário em kotlin para resolver essa tarefa em um projeto real kotlin multiplatform mobile com tudo que se faz necessário. (dependências, imports, estrutura de pacotes etc.) Neste vídeo super didático, te explico todo o passo-a-passo já usando compose:
Quer mais conteúdo assim?
Me siga, se você gosta desse tipo de conteúdo técnico didático e feito com muito carinho. ❤️
Você me encontra no 🐦twitter, 🦑 Github ou no 📺 Youtube onde tenho um curso gratuito “Do zero ao certificado Android”, outro chamado “Resoluções a problemas comuns Android” e Android Jetpack Compose disponíveis 0800 para você.
Para não perder nenhum conteúdo, inscreva-se nesse techblog clicando aqui: