アプリケーションで取り扱うデータのモデル(エンティティ)を作成します。
データモデルを管理するモジュールを作成します。
dataディレクトリ内にモジュールを作成したいのですが、プロジェクト直下にしかモジュールが作成できないため、プロジェクト直下に作成した後にdataディレクトリに移動させます。
modelモジュールの作成プロジェクト直下に新規モジュールを作成します。
modelモジュール情報の入力画面(Activity)は不要なのでAndroid Libraryを選択します。
Module nameは『model』にしています。
Package nameはdataディレクトリ内に移動させますので末尾は『data.model』にします。
『完了(F)』でモジュールが作成されます。
モジュールの移動作成したmodelモジュールを『リファクタリング(R)』で『ディレクトリの移動…』させます。
移動先の入力宛先ディレクトリに移動先の『data』ディレクトリを入力します。
『リファクタリング(R)』でモジュールが移動されます。
プロジェクト直下のsettings.gradle.ktsを編集して、プロジェクトパスを移動します。
1 2 |
: include( ":data:model" ) |
変更後、『Sync Now』で内容をプロジェクトに反映させます。
ソースコードの配置ディレクトリがjavaになっていますのので、kotlinに変更します。
※変更しなくても問題ありません。
appモジュールと同様に/data/model/src/main/javaディレクトリ、/data/model/src/androidTest/javaディレクトリ、および/data/model/src/test/javaディレクトリの名前を変更します。
バージョン情報や依存ライブラリ情報が自動的にバージョンカタログファイルに追記されていますので、内容を確認して必要に応じて修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[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にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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に追加します。
1 2 3 4 5 6 7 8 9 10 |
: gradlePlugin { : plugins { register( "LibraryConfigurePlugin" ) { id = "build.logic.library.configure" implementationClass = "LibraryConfigurePlugin" } } } |
最上位のbuild.gradle.ktsにライブラリモジュールプラグインの使用宣言が自動的に追記されていますので、必要に応じて修正します。
1 2 3 4 5 |
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へ対応させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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のバージョン定義を追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[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の参照を追記します。
1 2 3 4 5 |
: dependencies { implementation(libs.bundles.androidx.room) ksp(libs.room.compiler) } |
追記後、『Sync Now』で内容をプロジェクトに反映させます。
エンティティクラスを作成します。
エンティティクラスの作成/data/model/src/main/kotlin/…/data/modelを右クリックして『新規(N)』で『Kotlin ファイル/クラス』を作成します。
ファイル名の入力『クラス』を選択してファイル名を入力します。
下記が今回作成したエンティティになります。
設定(settings)テーブルのエンティティ定義です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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)テーブルのエンティティ定義です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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>が使用できるように型コンバータを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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 ) } |
データベースの定義を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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(リポジトリクラス)の定義を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
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) } } } |