LoginSignup
1
2

More than 3 years have passed since last update.

Sunflowerリポジトリで学ぶJetPack〜Room編

Last updated at Posted at 2020-07-26

新型コロナの影響で自宅待機になってしまい、その間勉強するものとしてSunflowerリポジトリを
勧めてもらいました。

JetPackのライブラリのうち、今回はRoom編です

尚、引用しているソースは明記しているところ以外は、基本的には全てSunflowerのリポジトリのものです。 

環境

  • 確認時はAndroid Studioのバージョンは 3.6.2を使用しました
  • JetPackAndroidXライブラリを利用するのでCompile SDK28以上にする必要があります

そもそもRoomってなに?

公式の説明

Room 永続ライブラリは SQLite 全体に抽象化レイヤを提供することで、
データベースへのより安定したアクセスを可能にし、
SQLite を最大限に活用できるようにします。

です!

Roomを使う理由

  • アプリ内ではオブジェクトを利用して、SQLiteのデータベースにアクセスできる。
  • コンパイル時にチェックができる

(SunflowerのリポジトリのトップページのRoomの説明より)

利用する際に必要なこと

必要なことは下記の4点です

  1. 依存関係の記載
  2. データベース作成
  3. エンティティ作成
  4. DAO作成

依存関係の記載

build.gradleに記載する内容

build.gradleには下記の依存関係の記載を行います。
(公式ドキュメントより)

build.gradle
    dependencies {
      def room_version = "2.2.3"

      implementation "androidx.room:room-runtime:$room_version"
      annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

      // optional - Kotlin Extensions and Coroutines support for Room
      implementation "androidx.room:room-ktx:$room_version"

      // optional - RxJava support for Room
      implementation "androidx.room:room-rxjava2:$room_version"

      // optional - Guava support for Room, including Optional and ListenableFuture
      implementation "androidx.room:room-guava:$room_version"

      // Test helpers
      testImplementation "androidx.room:room-testing:$room_version"
    }

  • 注意点
    • room-compilerの部分については、Kotlinで使用する場合はannotationProcessorではなく、kaptにします
  • オプション設定
    • Kotlin
    • RxJava
    • Guava
    • Test helpers
      • これらはオプションなので、利用する場合は記載します。

Sunflowerリポジトリの場合

Sunflowerリポジトリの場合はどのようになっているかを見てみましょう

build.gradle
buildscript {
    // Define versions in a single place
    ext {
                    :
        // App dependencies
                    :
        roomVersion = '2.1.0'
                    :
    }
                    :
}
app/build.gradle
dependencies {
    kapt "androidx.room:room-compiler:$rootProject.roomVersion"
                        :
    implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    implementation "androidx.room:room-ktx:$rootProject.roomVersion"
                        :
}

Sunflowerリポジトリの場合は、Kotlinのオプションのみ利用しているようです。

データベースのクラス作成

AppDatabase.kt(part1)
/**
 * The Room database for this app
 */
@Database(entities = [GardenPlanting::class, Plant::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun gardenPlantingDao(): GardenPlantingDao
    abstract fun plantDao(): PlantDao

データベースの設定を行うクラスをみてみましょう。
データベース利用の際の設定的なものは、ここの最初の部分に書かれています。

  • クラスの宣言に@Databaseアノテーションを記載します(必須)
    • データベースに関連付けられているエンティティのリストをアノテーション内に含む必要があり、entitiesにそれを記載します。(必須)
      • ここでは、GardenPlantingクラスとPlantクラスが記載されています。
    • versionはデータベースのバージョンを定義できます。マイグレーション処理を書く際はこちらのバージョンを参照できます(任意)
    • exportShemefalseに設定しています。(任意)
      • こちらはdefaulttrueに設定されます。
      • trueに設定された場合はroom.schemaLocationbuild.gradleに定義されている場合は、データベースのスキーマ情報をJSONファイルにエクスポートします。それを利用するとマイグレーションのテストが実施できます。
      • 詳しくはこちら
  • @TypeConverterアノテーションはentityに保存できない型がある場合(例えば、GardenPlantingクラスのplantDateCalender型))、変換処理を実装したクラスをつくり、そのクラス名を記載します。(任意)
    • ここでは、ConverterクラスにCalender ↔︎ long の相互変換処理が記載されており、そのConverterクラスを記載しています。
  • RoomDatabaseを拡張する抽象クラスとしてクラスを宣言します(必須)

    • abstract class AppDatabase : RoomDatabase() として宣言しています。
  • 引数が 0 で、@Dao アノテーション付きのクラスを返す抽象メソッドをクラス内に記載します(必須)

    • gardenPlantingDao()メソッドとplantDao()メソッドを記載してあります。
AppDatabase.kt(part2)
    companion object {

        // For Singleton instantiation
        @Volatile private var instance: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

こちらは、シングルトンで利用するための内容が記載されています。
すでにインスタンスが作られている場合は、作られているものを使い、ない場合は作るという処理です
スレッドセーフにするためにsynchronized()で排他制御を行っています。

AppDatabase.kt(part3)
        // Create and pre-populate the database. See this article for more details:
        // https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785
        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                    .addCallback(object : RoomDatabase.Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
                            WorkManager.getInstance(context).enqueue(request)
                        }
                    })
                    .build()
        }
    }
}

こちらでは、データベースを作成する処理です。
* Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME).buildでデータベースのインスタンスを作成します。
* その間にaddCallback()メソッドが入っています。
* こちらはデータベースが作成された時に実施されるコールバックメソッドです。
* 例えば、データベースの初期データなどを読み込む必要がある場合などで利用できます。
* ここではWorkManagerを利用し、バックグラウンド処理でjsonファイルから初期値の情報を読み込んで、データベースに保存しています。

エンティティの定義

それでは、SunflowerリポジトリのGardenPlantingクラスのエンティティ定義からみてみましょう

GardenPlanting.kt(part1)

@Entity(
    tableName = "garden_plantings",
    foreignKeys = [
        ForeignKey(entity = Plant::class, parentColumns = ["id"], childColumns = ["plant_id"])
    ],
    indices = [Index("plant_id")]
)
  • @Entityのアノテーションをつけてクラスを作成します。(必須)
    • tableNameに指定した名称が、SQLiteデータベース内のテーブル名になります。もし、なにも記載しなかった場合はクラス名がテーブル名になります。(任意)
    • foreignKeysは外部キー制約の定義を行います。ここでは、Plantクラスのidにある値のみ、plant_idに設定可能となります。(任意)
    • indicesで、このテーブルに設定するインデックスを指定します。この場合はplant_idをインデックスに設定しています。(任意)
GardenPlanting.kt(part2)
data class GardenPlanting(
    @ColumnInfo(name = "plant_id") val plantId: String,

    /**
     * Indicates when the [Plant] was planted. Used for showing notification when it's time
     * to harvest the plant.
     */
    @ColumnInfo(name = "plant_date") val plantDate: Calendar = Calendar.getInstance(),

    /**
     * Indicates when the [Plant] was last watered. Used for showing notification when it's
     * time to water the plant.
     */
    @ColumnInfo(name = "last_watering_date")
    val lastWateringDate: Calendar = Calendar.getInstance()
) {
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = "id")
        var gardenPlantingId: Long = 0
}
  • @ColumnInfoで各カラムの定義を行います。変数宣言などは通常の場合と同一です。(必須)
  • @ColumnInfoアノテーションの中のnameでカラム名を定義します。(任意)
    • テーブル名と同様に、指定しなかった場合は変数名がカラム名となります。
    • ここではplantId,plantDate,lastWateringDateの3つの引数を指定するコンストラクターを定義しています。
  • プライマリーキーには@PrimaryKeyのアノテーションをつけて変数を定義します。(任意)
    • カラム名id、変数名gardenPlantIdがプライマリーキーに指定されています。
    • ここではautoGenerate = trueを指定し、自動的に割り当てるようになっています。

DAOの定義

次にGardenPlantingDaoをみてみましょう。
DAOとはData Access Objectの略でデータベースの操作のインターフェースを提供するオブジェクトです。
使う側はデータベースの詳細を知ることなくデータの保存や取得が利用可能です。

GardenPlantingDao.kt(Part1)
/**
 * The Data Access Object for the [GardenPlanting] class.
 */
@Dao
interface GardenPlantingDao {
  • @Daoアノテーションをつけたインターフェースを定義します。(必須)
GardenPlantingDao.kt(Part2)
    @Query("SELECT * FROM garden_plantings")
    fun getGardenPlantings(): LiveData<List<GardenPlanting>>

    @Query("SELECT EXISTS(SELECT 1 FROM garden_plantings WHERE plant_id = :plantId LIMIT 1)")
    fun isPlanted(plantId: String): LiveData<Boolean>

    /**
     * This query will tell Room to query both the [Plant] and [GardenPlanting] tables and handle
     * the object mapping.
     */
    @Transaction
    @Query("SELECT * FROM plants WHERE id IN (SELECT DISTINCT(plant_id) FROM garden_plantings)")
    fun getPlantedGardens(): LiveData<List<PlantAndGardenPlantings>>
  • @Queryアノテーションは、データベースに対して読み書き処理を実行できます。
    • コンパイル時に検証されるので、クエリに問題があると、コンパイルエラーになります
    • 戻り値についても検証を行い、誤りがある場合は警告またはエラーを表示します
    • クエリにパラメータを渡す場合は、:[メソッドの引数名]としてクエリ内に記載します
      • ここでは2つめのクエリの中の:plantIdがそのように使われています
  • @Transactionアノテーションで記載したメソッドは、全て同一トランザクションで実行します
  • 各メソッドの戻り値にLiveDataを指定することができます。LiveDataRoomの組み合わせで、例えば、データベースのテーブルが更新された際にViewの更新などができます。詳しくはLiveData編をご覧ください
GardenPlantingDao.kt(Part3)

    @Insert
    suspend fun insertGardenPlanting(gardenPlanting: GardenPlanting): Long

    @Delete
    suspend fun deleteGardenPlanting(gardenPlanting: GardenPlanting)
}
  • @Insertアノテーションをつけたメソッドは、データベースに挿入する実装がRoomによって作成されます。
    • 複数のパラメータを指定することができます。
    • 全てのパラメータを同一のトランザクションで実行します
    • 戻り値を指定すると、挿入されるアイテムの新しいrowId になるlongの値を返すことができます。
  • @Deleteアノテーションをつけたメソッドは、こちらも、@Insertと同様に、データベースないの指定したエンティティのセットを削除する処理を作成します。
  • ここでは使用されていませんが、@Updateも同様です

  • メソッド名の前にsuspendがついているものは、コルーチン機能を使用した際に非同期かすることができ、メインスレッド上で実行される可能性がなくなります。(サブスレッドで実行して下さい、の意)

参考サイト

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2