AndroidStudio: Database(Room) の Coroutine を含めた使い方メモ Kotlin

参考URL

コルーチン

https://developer.android.com/kotlin/coroutines?hl=ja

Room

https://developer.android.com/jetpack/androidx/releases/room?hl=ja#groovy
https://developer.android.com/training/data-storage/room/referencing-data?hl=ja

Room を使うときはメインスレッドで使わないようにしましょうってやつ。

とりあえす今作ってるやつのをそのままコピペした。def あたりからが多分必要になる。 lifecycle らへんは正直いらないかも。

dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    def room_version = "2.4.1"

    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    kapt "androidx.lifecycle:lifecycle-compiler:$room_version"

    // optional - Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:$room_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$room_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$room_version"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$room_version"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$room_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$room_version"
    // optional - Test helpers
    testImplementation "androidx.room:room-testing:$room_version"

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
}

Room を使うにあたって必要なもの @Entity,@Dao,@TypeConverter,@Database 等

Entity

@Entity(tableName = "person_table")
data class PersonEntity (
    @PrimaryKey(autoGenerate = true)@ColumnInfo(name="id")var id:Long,
    @ColumnInfo(name="name")var name,
    @ColumnInfo(name="age")var age:Int,
    @ColumnInfo(name="gender")var gender:Gender,
)

enum class Gender {
    MALE,FEMALE,OTHER
}

今回 Enum を無理やり出したのはハマってしまった部分があったため。この Entity とかいうやつをもとにたぶんだがこの後 PersonDao_Impl.java みたいな感じの実装ファイルが生成される。その際にこの Gender の Enumクラスが同じ階層にないといけない。そうしないと文字化けエラーがでる。こんな感じのやつ。

�G���[: �N���X PersonEntity�̃R���X�g���N�^ PersonEntity�͎w�肳�ꂽ�^�ɓK�p�ł��܂���B
            _item = new PersonEntity();
                    ^
  ���Ғl: long,String,String,Gender
  ���o�l: ����������܂���
  ���R: ���������X�g�Ɖ��������X�g�̒������قȂ�܂�

今回自分がハマったところは app_name/entity/PersonEntity に Entity そのものを定義して、
Gender クラスを app_name/enum/Gender みたいな感じで定義したところ。

PersonDao_Impl.java は Gender クラスを参照できなかったらしい。

そのため Enum をテーブル内に入れるときは同じ階層にする必要がある。
Entity を定義したファイル内または app_name/entity にファイルを作って定義する。
app_name/entity/enum に定義するのはだめだった。
以前も layout で同じようなことした気がする。
Enum に TypeConverter はいらない。

Dao

@Dao
interface PersonDao {
    @Insert
    suspend fun insert(person:PersonEntity)

    @Query("select * from person_table")
    suspend fun selectAll():MutableList<PersonEntity>
}

ファイル名は PersonDao
正直今のところ LiveData を使う機会がないしめんどくさいので MutableList で返してしまっている。
Entity でつけたテーブル名を指定する。

ファイル名は PersonDao 。

TypeConverter

正直 TypeConverter を使う必要性がないため作っていないので公式。Date 型は現状 String でなんとかしちゃえしてるので本当に使うところがない。

https://developer.android.com/training/data-storage/room/referencing-data?hl=ja

Database

@Database(entities = [PersonEntity::class],version = 1,exportSchema = false)
@TypeConverters()
abstract class AppDatabase : RoomDatabase() {
    abstract fun PersonDao():PersonDao
    companion object {
        @Volatile
        private var database: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            if (database == null) {
                database = Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
            }
            return requireNotNull(database)
        }
    }
}

ファイル名は AppDatabase 。
複数テーブルがあるときは entities に カンマ区切りでいれる。

@Database(entities = [PersonEntity::class,SomeEntity::class],version = 1,exportSchema = false)

Application

class App:Application() {
    companion object {
        lateinit var database: AppDatabase
    }

    override fun onCreate() {
        super.onCreate()
        database = AppDatabase.getInstance(applicationContext)
    }
}

ファイル名は App 。

Repository

class PersonRepository() {
    private val dao = App.database.PersonDao()

    suspend fun insert(person:PersonEntity){
        withContext(Dispatchers.IO){
            dao.insert(todo)
        }
    }

    suspend fun selectAll():MutableList<PersonEntity>{
        return withContext(Dispatchers.IO){
            dao.selectAll()
        }
    }
}

Repository に関しては本当にこれで正しいのかわからない。ただあったから作ったみたいな感じ。役割の階層化が目的?
これ自体問題点は結構あるんじゃないかと思う。ほかのテーブル作ったときとかにテーブルを結合したりするとどのレポジトリに書けばいいかわからないし、三つ以上のテーブルとかになるとめちゃくちゃになりそう。

Activity 内での呼び出し(Repository 使用)

private var items = mutableListOf<PersonEntity>()
override fun onStart() {
    super.onStart()
    val todoRepository = TodoRepository()

    CoroutineScope(Dispatchers.Default).launch {
        items = personRepository.selectAll()
        CoroutineScope(Dispatchers.Main).launch {
            //ここで items を ListView とかにあてる
        }
    }
    Log.i("onStart ","start")
}

Activity 内での呼び出し(Ropositoryなし)

private var items = mutableListOf()
override fun onStart() {
    super.onStart()

    CoroutineScope(Dispatchers.Default).launch {
        CoroutineScope(Dispatchers.IO).launch {
            items = App.database.PersonDao().selectAll()
        }
        CoroutineScope(Dispatchers.Main).launch { 
            //割り当て
        }
    }
    Log.i("onStart ","start")
}

setOnClickListener とかでの呼び出し

button.setOnClickListener(){
        CoroutineScope(Dispatchers.Default).launch {
            personRepository.insert(
                PersonEntity(
                    0,
                    "aaa",
                    20,
                    GENDER.MALE
                )
            )
        }
}

たぶんこんな感じになると思う。Entity で autoGenerate = true にすると 0 を入れたとき増えてくれる。

終わりに

RecyclerView とか使いだすとどうしてもぶち当たるデータベース。SQLite 推奨しないならデフォルトで入れとけ。

メインスレッド内で宣言したものは別スレッドでは使えないのでちゃんと CoroutineScope をメインにする。onCreate()とかで宣言したやつね。

無駄に長くなってしまった。でも今後自分がデータベース使うとき困ることが減ると思う。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA