ユーザ用ツール

サイト用ツール


サイドバー

プログレス合同会社

広告

android:studio:application:data-model

05.データモデルの作成

アプリケーションで取り扱うデータのモデル(エンティティ)を作成します。

データモデルモジュールの作成

データモデルを管理するモジュールを作成します。

dataディレクトリ内にモジュールを作成したいのですが、プロジェクト直下にしかモジュールが作成できないため、プロジェクト直下に作成した後にdataディレクトリに移動させます。

android:studio:application:android-app4101.png

modelモジュールの作成

プロジェクト直下に新規モジュールを作成します。

android:studio:application:android-app4102.png

modelモジュール情報の入力

画面(Activity)は不要なのでAndroid Libraryを選択します。

Module nameは『model』にしています。

Package namedataディレクトリ内に移動させますので末尾は『data.model』にします。

完了(F)』でモジュールが作成されます。

android:studio:application:android-app4103.png

モジュールの移動

作成したmodelモジュールを『リファクタリング(R)』で『ディレクトリの移動…』させます。

android:studio:application:android-app4104.png

移動先の入力

宛先ディレクトリに移動先の『data』ディレクトリを入力します。

リファクタリング(R)』でモジュールが移動されます。

プロジェクト直下のsettings.gradle.ktsを編集して、プロジェクトパスを移動します。

   :
include(":data:model")

2行目
“:model”を“:data:model”に変更します。

変更後、『Sync Now』で内容をプロジェクトに反映させます。

ソースコードディレクトリの変更

ソースコードの配置ディレクトリがjavaになっていますのので、kotlinに変更します。
※変更しなくても問題ありません。

appモジュールと同様に/data/model/src/main/javaディレクトリ、/data/model/src/androidTest/javaディレクトリ、および/data/model/src/test/javaディレクトリの名前を変更します。

Composite Buildへの対応

バージョン情報や依存ライブラリ情報が自動的にバージョンカタログファイルに追記されていますので、内容を確認して必要に応じて修正します。

[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" }
   :

3行目~4行目
修正前の内容です。
5行目~6行目に修正しました。
バージョンはコメントのURLを参照して確認します。
10行目~11行目
修正前の内容です。
12行目~13行目に修正しました。
17行目
修正前の内容です。
18行目に修正しました。

修正後、『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())
      }
    }
  }
}

11行目~14行目(pluginManager)
ライブラリモジュールで使用するプラグインを定義しています。
17行目
ConfigureExtension.ktで作成した全モジュールの共通定義を呼び出しています。
18行目~26行目
/data/model/build.gradle.kts内のandroidブロックを参照して指定します。
29行目~31行目、33行目~35行目
/data/model/build.gradle.kts内のdependenciesブロックの依存ライブラリを定義しています。
バージョンカタログを参照しています。
32行目、36行目
Hiltの依存ライブラリをバージョンカタログを参照して定義しています。

作成したビルドプラグインをライブラリモジュールのbuild.gradle.ktsで使用できるようにbuild-logic直下のbuild.gradle.ktsに追加します。

   :
gradlePlugin {
   :
  plugins {
    register("LibraryConfigurePlugin") {
      id = "build.logic.library.configure"
      implementationClass = "LibraryConfigurePlugin"
    }
  }
}

5行目
任意の名前で名称を定義しています。
6行目
任意の名前でプラグインのidを定義しています。
7行目
プラグインを実装しているクラスを指定します。
src/main/kotlin内に作成したLibraryConfigurePlugin.ktで定義しているクラス名です。

最上位のbuild.gradle.ktsにライブラリモジュールプラグインの使用宣言が自動的に追記されていますので、必要に応じて修正します。

plugins {
   :
  alias(libs.plugins.com.android.library) apply false
  alias(libs.plugins.android.library) apply false
}

3行目
修正前の内容です。
4行目に修正しました。

追記後、『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)
}

2行目~3行目、9行目~30行目、35行目~40行目
ビルドプラグインで定義しているので削除します。
4行目
/build-logic/build.gradle.ktsで定義したライブラリモジュール用ビルドプラグインのidを指定します。

変更後、『Sync Now』で内容をプロジェクトに反映させます。

Roomの導入

データベースの処理は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" ]
   :

3行目[versions]
mavenCentralリポジトリの最新安定バージョンを指定します。
7行目~9行目[libraries]
Roomライブラリモジュールとバージョンを関連付けします。
12行目[bundles]
Roomライブラリモジュールをグループ化します。

追記後、『Sync Now』で内容をプロジェクトに反映させます。

data/modelのbuild.gradle.ktsRoomの参照を追記します。

   :
dependencies {
  implementation(libs.bundles.androidx.room)
  ksp(libs.room.compiler)
}

3行目
グループ化したRoomライブラリをバージョンカタログファイルから参照しています。
4行目
KSPの依存ライブラリをバージョンカタログファイルから参照しています。

追記後、『Sync Now』で内容をプロジェクトに反映させます。

エンティティの作成

エンティティクラスを作成します。

android:studio:application:android-app4201.png

エンティティクラスの作成

/data/model/src/main/kotlin/…/data/modelを右クリックして『新規(N)』で『Kotlin ファイル/クラス』を作成します。

android:studio:application:android-app4202.png

ファイル名の入力

クラス』を選択してファイル名を入力します。

下記が今回作成したエンティティになります。

設定(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?
)

1行目
パッケージ名のプロジェクト部分は適宜変更してください。
7行目
データベース上のテーブル名をsettingsにしています。
10行目、12行目
データベース上のカラム名を定義しています。
11行目、13行目
アプリケーションでのカラム名と属性を定義しています。

抽選数字(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>
)

19行目~22行目
lottery_numberrのみを全件取得したい場合にList<List<Int»が戻り値に定義できないため、投影(Projection)クラスを作成します。
投影(Projection)は主に複数の列をまとめる目的で使用されます。
列定義はエンティティ定義と合わせます。

型コンバーターの作成

Roomで使用できるデータ型は、SQLiteの4つのプリミティブ型(INTEGER、REAL、TEXT、BLOB)のみで、エンティティで定義したLocalDateList<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(",")
  }
}

6行目
任意のクラス名にします。
7行目~10行目
String型からLocalDate型に変換する関数(型コンバーター)を定義しています。
11行目~14行目
LocalDate型からString型に変換する関数(型コンバーター)を定義しています。
16行目~19行目
String型からList<Int>型に変換する関数(型コンバーター)を定義しています。
20行目~23行目
List<Int>型からString型に変換する関数(型コンバーター)を定義しています。

DAOの作成

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)
}

10行目
任意のクラス名にします。
11行目~12行目
itemNameからitem_valueを取得する関数を定義しています。
14行目~15行目
Settingsエンティティでsettingsテーブルに挿入する関数を定義しています。
17行目~18行目
Settingsエンティティでsettingsテーブルを更新する関数を定義しています。
20行目~21行目
settingsテーブルのレコードを全件削除する関数を定義しています。
23行目~24行目
itemNameでsettingsテーブルの該当レコードを削除する関数を定義しています。
onConflict = OnConflictStrategy.REPLACE
Conflictが発生した場合、置き換えるようにしています。

抽選数字(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)
}

9行目
任意のクラス名にします。
10行目~11行目
lotteryテーブルの全件を取得する関数を定義しています。
13行目~14行目
lotteryテーブルからlottery_numberのみ全件を取得する関数を定義しています。
投影(Projection)クラスを使用しています。
16行目~17行目
Lotteryエンティティでlotteryテーブルに挿入する関数を定義しています。
19行目~20行目
lotteryテーブルのレコードを全件削除する関数を定義しています。
22行目~25行目
lotteryテーブルの先頭から指定数のレコードを削除する関数を定義しています。

データベースの定義

データベースの定義を行います。

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
}

9行目
データベースで使用するエンティティを指定します。
10行目
データベースで使用する型コンバーターを指定します。
12行目
インスタンスが1つしか存在しないことを保証するクラス(Singleton)を宣言しています。
14行目~15行目
データベースインスタンスは複数スレッドからアクセスされる可能性があるため@Volatile定義しています。
22行目
移行するときに古いデータベースを破棄して再構築します。
28行目~29行目
データベースで使用するDAOを指定します。

データベースAPIの定義

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)
    }
  }
}

suspend、withContext
後述するKotlin Coroutinesによる非同期での実行を宣言しています。

android/studio/application/data-model.txt · 最終更新: 2023/12/13 by プログレス合同会社