Roomのデータベース定義では
@Database(
entities = [Settings::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun SettingsDao(): SettingsDao
:
}
エンティティやDAOの追加、削除等による列挙ミスを防止するため、列挙するエンティティやDAOを自動生成するKSPプロセッサープラグインを作成します。
エンティティやDAOの自動生成に必要なKSPライブラリをバージョンカタログに追記します。
[versions]
:
# ./gradlew -versionで表示されるKotlinのバージョンに適した最新バージョンにします(自動で上げない)
#noinspection GradleDependency,NewerVersionAvailable
gradle-ksp = "2.0.21-1.0.28" # https://mvnrepository.com/artifact/com.google.devtools.ksp/symbol-processing-api
kotlin-poet = "2.2.0" # https://mvnrepository.com/artifact/com.squareup/kotlinpoet
:
[libraries]
:
gradle-ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "gradle-ksp" }
kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlin-poet" }
kotlin-poet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
:
room-common = { module = "androidx.room:room-common", version.ref = "room" }
:
追記後、『Sync Now』で内容をプロジェクトに反映させます。
KSPプロセッサーモジュール:core:database:kspをAndroid Studioのモジュール作成ウィザードを使わずにモジュールを作成します。
/core/database直下にkspディレクトリを追加し、その直下にkotlinスクリプトbuild.gradle.ktsを作成してモジュール化します。
plugins {
alias(libs.plugins.kotlin.jvm)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get().toInt()))
}
}
dependencies {
implementation(libs.gradle.ksp)
implementation(libs.kotlin.poet)
implementation(libs.kotlin.poet.ksp)
implementation(libs.room.common)
}
モジュール作成ウィザードを使わずにモジュールを作成したため、ビルド対象モジュールに手動で追記します。
:
include(":core:database")
include(":core:database:ksp")
:
追記後、『Sync Now』で内容をプロジェクトに反映させます。
:core:database:kspモジュールにsrc/main/kotlinディレクトリを作成し、jp.co.progress_llc.portal.core.database.ksp.modelパッケージ(ディレクトリ)を追加します。
作成したパッケージ内にエンティティコレクター用のデータモデルEntityModel.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp.model
data class EntityModel(
val packageName: String,
val simpleName: String
) {
val qualifiedName: String = "$packageName.$simpleName"
}
modelパッケージと同階層にcollectorパッケージ(ディレクトリ)を追加します。
作成したパッケージ内にエンティティコレクターEntityCollector.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp.collector
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration
import jp.co.progress_llc.portal.core.database.ksp.model.EntityModel
class EntityCollector(
private val resolver: Resolver,
private val logger: KSPLogger
) {
fun collect(): List<EntityModel> {
return resolver
.getSymbolsWithAnnotation("androidx.room.Entity")
.filterIsInstance<KSClassDeclaration>()
.map { entity ->
EntityModel(
packageName = entity.packageName.asString(),
simpleName = entity.simpleName.asString()
)
}
.toList()
}
}
modelパッケージ内にDAOコレクター用のデータモデルDaoModel.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp.model
data class DaoModel(
val packageName: String,
val simpleName: String
) {
val qualifiedName: String = "$packageName.$simpleName"
val functionName: String = simpleName.replaceFirstChar { it.lowercaseChar() }
}
collectorパッケージ内ににDAOコレクターDaoCollector.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp.collector
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSClassDeclaration
import jp.co.progress_llc.portal.core.database.ksp.model.DaoModel
class DaoCollector (
private val resolver: Resolver,
private val logger: KSPLogger
) {
fun collect(): List<DaoModel> {
return resolver
.getSymbolsWithAnnotation("androidx.room.Dao")
.filterIsInstance<KSClassDeclaration>()
.map { dao ->
DaoModel(
packageName = dao.packageName.asString(),
simpleName = dao.simpleName.asString()
)
}
.toList()
}
}
collectorパッケージと同階層にgeneratorパッケージ(ディレクトリ)を追加します。
追加したパッケージ内にRoomデータベースを生成するkotlinクラスファイルAppDatabase.ktを自動生成するAppDatabaseGenerator.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp.generator
import com.google.devtools.ksp.processing.CodeGenerator
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ksp.writeTo
import jp.co.progress_llc.portal.core.database.ksp.model.EntityModel
import jp.co.progress_llc.portal.core.database.ksp.model.DaoModel
class AppDatabaseGenerator (
private val codeGenerator: CodeGenerator,
private val entities: List<EntityModel>,
private val daos: List<DaoModel>
) {
fun generate() {
val fileSpec = FileSpec.builder(
packageName = "jp.co.progress_llc.portal.core.database",
fileName = "AppDatabase"
)
.addType(createDatabaseType())
.build()
fileSpec.writeTo(codeGenerator, aggregating = true)
}
private fun createDatabaseType(): TypeSpec {
return TypeSpec.classBuilder("AppDatabase")
.addModifiers(KModifier.ABSTRACT)
.addAnnotation(createDatabaseAnnotation())
.superclass(
ClassName("androidx.room", "RoomDatabase")
)
.addFunctions(createDaoFunctions())
.build()
}
private fun createDatabaseAnnotation(): AnnotationSpec {
val databaseClass = ClassName("androidx.room", "Database")
val entitiesCode = entities.joinToString(",\n") {
"%T::class"
}
val entityClassNames = entities.map {
ClassName(it.packageName, it.simpleName)
}
return AnnotationSpec.builder(databaseClass)
.addMember(
"entities = [%L]",
CodeBlock.builder()
.add(
entitiesCode,
*entityClassNames.toTypedArray()
)
.build()
)
.addMember("version = %L", 1)
.addMember("exportSchema = %L", false)
.build()
}
private fun createDaoFunctions(): List<FunSpec> {
return daos.map { dao ->
FunSpec.builder(dao.functionName)
.addModifiers(KModifier.ABSTRACT)
.returns(
ClassName(dao.packageName, dao.simpleName)
)
.build()
}
}
}
AppDatabaseGeneratorを呼び出すKSPプロセッサーDatabaseProcessor.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.KSAnnotated
import jp.co.progress_llc.portal.core.database.ksp.collector.EntityCollector
import jp.co.progress_llc.portal.core.database.ksp.collector.DaoCollector
import jp.co.progress_llc.portal.core.database.ksp.generator.AppDatabaseGenerator
class DatabaseProcessor(
private val env: SymbolProcessorEnvironment
) : SymbolProcessor {
private val logger = env.logger
private val codeGenerator = env.codeGenerator
override fun process(resolver: Resolver): List<KSAnnotated> {
val entities = EntityCollector(resolver, logger).collect()
val daos = DaoCollector(resolver, logger).collect()
if (entities.isEmpty()) {
return emptyList()
}
logger.warn(" ${entities.size} @Entity found.")
AppDatabaseGenerator(
codeGenerator = codeGenerator,
entities = entities,
daos = daos
).generate()
return emptyList()
}
}
DatabaseProcessorをKSPに提供するプロバイダーDatabaseProcessorProvider.ktを作成します。
package jp.co.progress_llc.portal.core.database.ksp
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class DatabaseProcessorProvider : SymbolProcessorProvider {
override fun create(
environment: SymbolProcessorEnvironment
): SymbolProcessor {
return DatabaseProcessor(environment)
}
}
プロバイダーをKSPのサービスに登録して実行されるようにします。
/src/mainセットにresources/META-INF/servicesディレクトリを追加し、直下にcom.google.devtools.ksp.processing.SymbolProcessorProviderを作成します。
jp.co.progress_llc.portal.core.database.ksp.DatabaseProcessorProvider
:build-logicモジュールにKspDatabaseプラグインKspDatabasePlugin.ktを作成します。
package jp.co.progress_llc.portal.buildlogic.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.dependencies
class KspDatabasePlugin : Plugin<Project> {
override fun apply(project: Project) = with(project) {
apply(plugin = "com.google.devtools.ksp")
dependencies {
add("ksp", project(":core:database:ksp"))
}
}
}
:build-logicモジュールのbuild.gradle.ktsにKspDatabaseプラグインを追記します。
:
gradlePlugin {
:
plugins {
register("KspDatabasePlugin") {
id = "build.logic.ksp.database"
implementationClass = "jp.co.progress_llc.portal.buildlogic.plugins.KspDatabasePlugin"
}
}
:
追記後、『Sync Now』で内容をプロジェクトに反映させます。
Roomデータベースを定義している:core:databaseモジュールにKspDatabaseプラグインを適用します。
plugins {
:
id("build.logic.ksp.database")
}
:
追記後、『Sync Now』で内容をプロジェクトに反映させます。
./gradlew clean :core:database:build
を実行してbuild/generated/ksp/release/kotlin/jp/co/progress_llc/portal/core/databaseにAppDatabase.ktが作成されていることを確認します。