Roomのデータベース定義では
@Database( entities = [Settings::class], version = 1, exportSchema = false )
データベース定義は:appモジュールで行いますが、そうすると各機能モジュールのエンティティを:appモジュールが知る必要があり、モジュール結合を疎にするという設計方針から外れてしまいます。
そこで、各機能モジュールのエンティティ定義を自動で収集するKSP(Kotlin Symbol Processing)を使ったエンティティコレクターを作成することにします。
エンティティコレクターに必要なKSPライブラリをバージョンカタログに追記します。
[versions]
:
# ./gradlew -versionで表示されるKotlinのバージョンに適した最新バージョンにします(自動で上げない)
#noinspection 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" }
:
追記後、『Sync Now』で内容をプロジェクトに反映させます。
エンティティコレクターは、ビルド用の機能ですので、/build-logicディレクトリ内に作成します。
/build-logic直下にkspディレクトリを追加し、直下にkotlinスクリプトbuild.gradle.ktsを作成してモジュール化します。
plugins {
`kotlin-dsl`
}
dependencies {
implementation(libs.gradle.ksp)
implementation(libs.kotlin.poet)
}
/build-logic直下のsettings.gradle.ktsに:build-logic:kspモジュールをビルド対象として追記します。
:
include(":ksp")
:build-logic:kspモジュールにsrc/main/kotlinディレクトリを作成し、jp/co/progress_llc/portal/buildlogic/kspパッケージ(ディレクトリ)を追加します。
作成したパッケージ内にRoomEntityCollector.ktを作成します。
package jp.co.progress_llc.portal.buildlogic.ksp
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import kotlin.reflect.KClass
/**
* Roomの@Entityアノテーションが付いたクラスを収集するKSPプロセッサー
*/
class RoomEntityCollector(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
// Roomの@Entityアノテーションが付いたクラスを収集します
val entities = resolver
.getSymbolsWithAnnotation("androidx.room.Entity")
.filterIsInstance<KSClassDeclaration>()
.toList()
logger.info("RoomEntityCollector: found ${entities.size} entities")
if (entities.isEmpty()) return emptyList()
// 収集したクラスをファイルへ書き出します
val arrayContent = entities.joinToString(",\n") {
it.qualifiedName!!.asString() + "::class"
}
val kClassStar = KClass::class.asClassName().parameterizedBy(STAR)
val typeSpec = TypeSpec.objectBuilder("RoomEntities")
.addProperty(
PropertySpec.builder(
"entities",
ARRAY.parameterizedBy(kClassStar)
)
.initializer("arrayOf(\n$arrayContent\n)")
.build()
)
.build()
val fileSpec = FileSpec.builder(
"jp.co.progress_llc.portal.buildlogic.ksp.generated",
"RoomEntities"
)
.addType(typeSpec)
.build()
codeGenerator.createNewFile(
Dependencies(false),
"jp.co.progress_llc.portal.buildlogic.ksp.generated",
"RoomEntities"
).writer().use {
fileSpec.writeTo(it)
}
return emptyList()
}
}
エンティティコレクターをKSPに提供するプロバイダーRoomEntityCollectorProvider.ktを作成します。
package jp.co.progress_llc.portal.buildlogic.ksp
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
/**
* RoomEntityCollectorのプロバイダー
*/
class RoomEntityCollectorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return RoomEntityCollector(
codeGenerator = environment.codeGenerator,
logger = environment.logger
)
}
}
プロバイダーをKSPのサービスに登録してインスタンス化します。
:build-logic:kspモジュールの/src/mainセットにresources/META-INF/servicesディレクトリを追加し、直下にcom.google.devtools.ksp.processing.SymbolProcessorProviderを作成します。
jp.co.progress_llc.portal.buildlogic.ksp.RoomEntityCollectorProvider