アプリケーションで取り扱うデータのモデル(エンティティ)を作成します。
データモデルを管理するモジュールを作成します。
dataディレクトリ内にモジュールを作成したいのですが、プロジェクト直下にしかモジュールが作成できないため、プロジェクト直下に作成した後にdataディレクトリに移動させます。
modelモジュールの作成プロジェクト直下に新規モジュールを作成します。
modelモジュール情報の入力画面(Activity)は不要なのでAndroid Libraryを選択します。
Module nameは『model』にしています。
Package nameはdataディレクトリ内に移動させますので末尾は『data.model』にします。
『完了(F)』でモジュールが作成されます。
モジュールの移動作成したmodelモジュールを『リファクタリング(R)』で『ディレクトリの移動…』させます。
移動先の入力宛先ディレクトリに移動先の『data』ディレクトリを入力します。
『リファクタリング(R)』でモジュールが移動されます。
プロジェクト直下のsettings.gradle.ktsを編集して、プロジェクトパスを移動します。
: include(":data:model")
変更後、『Sync Now』で内容をプロジェクトに反映させます。
ソースコードの配置ディレクトリがjavaになっていますのので、kotlinに変更します。
※変更しなくても問題ありません。
appモジュールと同様に/data/model/src/main/javaディレクトリ、/data/model/src/androidTest/javaディレクトリ、および/data/model/src/test/javaディレクトリの名前を変更します。
バージョン情報や依存ライブラリ情報が自動的にバージョンカタログファイルに追記されていますので、内容を確認して必要に応じて修正します。
[versions] : appcompat = "1.6.1" material = "1.10.0" androidx-appcompat = "1.6.1" # https://mvnrepository.com/artifact/androidx.appcompat/appcompat google-android-material = "1.10.0" # https://mvnrepository.com/artifact/com.google.android.material/material [libraries] : appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } google-android-material = { module = "com.google.android.material:material", version.ref = "google-android-material" } [plugins] : com-android-library = { id = "com.android.library", version.ref = "android-application" } android-library = { id = "com.android.library", version.ref = "android-application" } :
修正後、『Sync Now』で内容をプロジェクトに反映させます。
/build-logic/src/main/kotlin内にライブラリモジュールのbuild.gradle.ktsで使用するビルドプラグインを作成します。
ファイル名をLibraryConfigurePlugin.ktにしています。
import com.android.build.api.dsl.LibraryExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies class LibraryConfigurePlugin: Plugin<Project> { override fun apply(project: Project) { with(project) { with(pluginManager) { apply("com.android.library") apply("org.jetbrains.kotlin.android") apply("com.google.devtools.ksp") apply("com.google.dagger.hilt.android") } extensions.configure<LibraryExtension> { configureCommonExtension(this) defaultConfig { consumerProguardFiles("consumer-rules.pro") } buildTypes { release { isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } } dependencies { add("implementation", catalog.findLibrary("androidx.core.ktx").get()) add("implementation", catalog.findLibrary("androidx.appcompat").get()) add("implementation", catalog.findLibrary("google-android-material").get()) add("implementation", catalog.findLibrary("hilt.android").get()) add("testImplementation", catalog.findLibrary("junit").get()) add("androidTestImplementation", catalog.findLibrary("androidx.test.ext.junit").get()) add("androidTestImplementation", catalog.findLibrary("androidx.test.espresso.core").get()) add("ksp", catalog.findLibrary("hilt.compiler").get()) } } } }
作成したビルドプラグインをライブラリモジュールのbuild.gradle.ktsで使用できるようにbuild-logic直下のbuild.gradle.ktsに追加します。
: gradlePlugin { : plugins { register("LibraryConfigurePlugin") { id = "build.logic.library.configure" implementationClass = "LibraryConfigurePlugin" } } }
最上位のbuild.gradle.ktsにライブラリモジュールプラグインの使用宣言が自動的に追記されていますので、必要に応じて修正します。
plugins { : alias(libs.plugins.com.android.library) apply false alias(libs.plugins.android.library) apply false }
追記後、『Sync Now』で内容をプロジェクトに反映させます。
data/modelモジュールのbuild.gradle.ktsをComposite Buildへ対応させます。
plugins { alias(libs.plugins.com.android.library) alias(libs.plugins.kotlin.android) id("build.logic.library.configure") } android { namespace = "jp.co.example.android01.data.model" compileSdk = 33 defaultConfig { minSdk = 27 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") } buildTypes { release { isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.appcompat) implementation(libs.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.androidx.test.espresso.core) }
変更後、『Sync Now』で内容をプロジェクトに反映させます。
データベースの処理はRoomを使用します。
バージョンカタログファイルにRoomのバージョン定義を追記します。
[versions] : androidx-room = "2.6.0" [libraries] : room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } [bundles] androidx-room = [ "room-runtime", "room-ktx" ] :
追記後、『Sync Now』で内容をプロジェクトに反映させます。
data/modelのbuild.gradle.ktsにRoomの参照を追記します。
: dependencies { implementation(libs.bundles.androidx.room) ksp(libs.room.compiler) }
追記後、『Sync Now』で内容をプロジェクトに反映させます。
エンティティクラスを作成します。
エンティティクラスの作成/data/model/src/main/kotlin/…/data/modelを右クリックして『新規(N)』で『Kotlin ファイル/クラス』を作成します。
ファイル名の入力『クラス』を選択してファイル名を入力します。
下記が今回作成したエンティティになります。
設定(settings)テーブルのエンティティ定義です。
package jp.co.example.Exercise01.data.model import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "settings") data class Settings( @PrimaryKey @ColumnInfo(name = "item_name") val itemName: String, @ColumnInfo(name = "item_value") val itemValue: String? )
抽選数字(lottery)テーブルのエンティティ定義です。
package jp.co.example.Exercise01.data.model import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.time.LocalDate @Entity(tableName = "lottery") data class Lottery( @PrimaryKey @ColumnInfo(name = "lottery_times") val lotteryTimes: String, @ColumnInfo(name = "lottery_date") val lotteryDate: LocalDate, @ColumnInfo(name = "lottery_number") val lotteryNumber: List<Int> ) data class LotteryNumber( @ColumnInfo(name = "lottery_number") val lotteryNumber: List<Int> )
Roomで使用できるデータ型は、SQLiteの4つのプリミティブ型(INTEGER、REAL、TEXT、BLOB)のみで、エンティティで定義したLocalDateやList<Int>は、このままでは使用できません。
型コンバーターを作成することにより、4つのプリミティブ型以外のカスタムデータ型が使用できるようになります。
LocalDateとList<Int>が使用できるように型コンバータを作成します。
package jp.co.example.Exercise01.data.model import androidx.room.TypeConverter import java.time.LocalDate internal class Converters { @TypeConverter fun LocalDateFromString(value: String?): LocalDate? { return value?.let { LocalDate.parse(it) } } @TypeConverter fun LocalDateToString(value: LocalDate?): String? { return value?.toString() } @TypeConverter fun IntListFromString(value: String?): List<Int>? { return value?.split(",")?.map {it.toInt() } } @TypeConverter fun IntListToString(value: List<Int>?): String? { return value?.joinToString(",") } }
Roomデータベースにアクセスするインターフェース定義DAO(Data Access Object)を作成します。
設定(settings)テーブルにアクセスするDAOを作成します。
package jp.co.example.exercise01.data.model import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update @Dao interface SettingsDao { @Query("SELECT item_value FROM settings WHERE item_name = :itemName") fun getItemValue(itemName: String): String? @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(fields: Settings) @Update(onConflict = OnConflictStrategy.REPLACE) fun update(fields: Settings) @Query("DELETE FROM settings") fun deleteAll() @Query("DELETE FROM settings WHERE item_name = :itemName") fun deleteItem(itemName: String) }
抽選数字(lottery)テーブルにアクセスするDAOを作成します。
package jp.co.example.exercise01.data.model import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query @Dao interface LotteryDao { @Query("SELECT * FROM lottery ORDER BY lottery_times") fun getAll(): List<Lottery>? @Query("SELECT lottery_number FROM lottery ORDER BY lottery_times") fun getNumberList(): List<LotteryNumber>? @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(fields: Lottery) @Query("DELETE FROM lottery") fun deleteAll() @Query("DELETE FROM lottery WHERE lottery_times = " + "(SELECT lottery_times FROM lottery ORDER BY lottery_times LIMIT :rowCount)" ) fun deleteOldestRow(rowCount: Int) }
データベースの定義を行います。
package jp.co.example.exercise01.data.model import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters @Database(entities = [Settings::class, Lottery::class], version = 1, exportSchema = false) @TypeConverters(Converters::class) abstract class LotteryDatabase: RoomDatabase() { companion object { private const val DB_NAME = "lottery.db" @Volatile private var INSTANCE: LotteryDatabase? = null fun getDatabase(context: Context): LotteryDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, LotteryDatabase::class.java, DB_NAME ).fallbackToDestructiveMigration().build() INSTANCE = instance return instance } } } internal abstract fun SettingsDao(): SettingsDao internal abstract fun LotteryDao(): LotteryDao }
DAOをアプリケーションから隠蔽するため、データベースAPI(リポジトリクラス)の定義を行います。
package jp.co.example.exercise01.data.model import android.content.Context import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class DbAPI(private val context: Context) { private val settingsDao: SettingsDao by lazy { LotteryDatabase.getInstance(context).SettingsDao() } private val lotteryDao: LotteryDao by lazy { LotteryDatabase.getInstance(context).LotteryDao() } suspend fun getSettingItem(itemName: String): String? { return withContext(Dispatchers.Main) { settingsDao.getItemValue(itemName) } } suspend fun insertSetting(fields: Settings) { withContext(Dispatchers.Main) { settingsDao.insert(fields) } } suspend fun updateSetting(fields: Settings) { withContext(Dispatchers.Main) { settingsDao.update(fields) } } suspend fun deleteAllSetting() { withContext(Dispatchers.Main) { settingsDao.deleteAll() } } suspend fun deleteSettingItem(itemName: String) { withContext(Dispatchers.Main) { settingsDao.deleteItem(itemName) } } suspend fun getAllLottery(): List<Lottery>? { return withContext(Dispatchers.Main) { lotteryDao.getAll() } } suspend fun getLotteryNumberList(): List<LotteryNumber>? { return withContext(Dispatchers.Main) { lotteryDao.getNumberList() } } suspend fun insertLottery(fields: Lottery) { withContext(Dispatchers.Main) { lotteryDao.insert(fields) } } suspend fun deleteAllLottery() { withContext(Dispatchers.Main) { lotteryDao.deleteAll() } } suspend fun deleteOldestRowLottery(rowCount: Int) { withContext(Dispatchers.Main) { lotteryDao.deleteOldestRow(rowCount) } } }