みんなRoom使ってますか?Room。
自分は最近まで使ったことなかったんだけど、ここ2~3案件Roomを使ってるアプリ案件に配属されて触れてました。
Roomの改修とかはしたことない状態で、なんとなく自分のアプリに1から導入してみようって感じで入れてみた。
そもそもRoomってなんぞや?って話なんだけど、SQLiteを良い感じにラップしてくれるライブラリって感じで思ってくれれば良いと思う。
自分はそう思ってるw
Roomは単純に考えると以下のクラスを作っておけば良い
・Databaseクラス
・Entityクラス
・Daoクラス
お手軽!
作る順番はEntity→Dao→Databaseが良いかなって感じた。
この順番なら基本的にビルドエラーは起こらない、、、はず!
ってことで諸々、どんなクラスを作っていくのか書いていくよ。
あまりRoomについてあまり調べてないから自分の解釈で書いていくので間違ってたらごめんなさいw
ちなみにここから晒すコードはこの個人アプリで使ってるクラスを少し編集したものですw
導入手順
依存
以下をappのbuild.gradleに追加する
// Room
def room_version = "2.2.0-rc01"
// For Kotlin use kapt instead of annotationProcessor
implementation "androidx.room:room-runtime:$room_version"
// annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
Entityクラスを作成する
EntityクラスはDBのテーブルに該当クラスで、フィールド変数はカラムに該当する。
// ユーザー情報とか
@Entity(primaryKeys = ["idStr", "date"])
data class UserEntity(
// UserId
val idStr: String,
// 2019-09-26形式で入れる
val date: String,
val user: User,
val follow: List<String>,
val follower: List<String>,
// 更新時間
val datetime: Long = System.currentTimeMillis()
)
テーブル名とカラムを定義する。
PrimaryKeyが1つで良いのであれば、@primaryKeyを変数の上につければ良い。
今回は複数のPrimaryKeyを付けたかったのでクラスの上にアノテーションを付けている。
UserとかList<String>ってSQLiteの型に無いけど大丈夫?
大丈夫!・・・・のようにする
SQLiteに無い型に対応する
SQLiteってざっくりいうとINTEGER,TEXT,REAL,BLOBの4つの型しか無い。
なので↑で書いたようにUserとかList<String> とかは入れられない。
ってことで、任意の型をSQLiteで認識できる型に変換しちゃおうぜ!ってことがRoomではできる。
以下のようなコンバータークラスを作成する。
// Room型変換
internal class RoomTypeConverters {
companion object {
@JvmStatic
@TypeConverter
fun fromUserJson(userJson: String) = Gson().fromJson(userJson, User::class.java)
@JvmStatic
@TypeConverter
fun userToJson(user: User) = Gson().toJson(user)
@JvmStatic
@TypeConverter
fun commaStringToList(commaString: String): List<String> =
commaString.split(",").map { it.trim() }
@JvmStatic
@TypeConverter
fun listToComma(list: List<String>) = list.toTypedArray().joinToString(",")
}
}
今回はとりあえず、 UserとかList<String> はStringにしちゃってTEXTで認識してもらうようにしようぜ!って感じ。
UserはGson使ってJsonで保存。Listはカンマ区切りにして保存。
取得するときは逆変換。
何となくこれで今のところうまく行っているw
これ書いていて、UserIdとかDateとかListで返ってくるんじゃない・・・?とか思ってしまったけど、、、
個人アプリではDateに関しては今の所大丈夫そうだけど、、、。これ書き終わったら確認してみよう(いましろやw
Daoクラスを作成する
DBに直接アクセスするクラスですね。
ここにSQL分を書いたりします。
生SQL書くのでSQLiteのよくわからない(自分だけ?)記述をしなくても良い!
// ユーザーDAO
@Dao
interface UserEntityDao {
@Query("select * from UserEntity where idStr = :idStr order by datetime desc")
fun getAll(idStr: String): List<UserEntity>
@Query("select * from UserEntity where idStr = :idStr order by datetime desc limit 2")
fun get2DayData(idStr: String): List<UserEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(userEntity: UserEntity)
@Query("delete from UserEntity")
fun deleteAll()
}
まぁ、欲しいメソッド作って、SQLを書くだけですw
Databaseクラスを作成する
Entity,Converter,Daoとかを書き書きします。
// DB
@Database(entities = [UserEntity::class], version = 1)
@TypeConverters(RoomTypeConverters::class)
abstract class KataomoiDatabase : RoomDatabase() {
abstract fun userEntityDao(): UserEntityDao
companion object {
private const val dbName = "kataomoi.db"
private var instance: KataomoiDatabase? = null
fun getInstance(context: Context): KataomoiDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context, KataomoiDatabase::class.java, dbName)
.fallbackToDestructiveMigration()
.build()
}
return requireNotNull(instance)
}
}
}
アノテーションで使うEntity,Converterを設定すればOK!
使い方
ここまでだと使い方がよくわからんってことになると思うから一応書いておく。
初期化
個人アプリの場合はApplicationクラスにDBのインスタンスを持たせています。
// App
class KataomoiApplication : Application() {
companion object {
lateinit var db: KataomoiDatabase
}
override fun onCreate() {
super.onCreate()
db = KataomoiDatabase.getInstance(applicationContext)
}
}
なにか取得してみる
// 取得
GlobalScope.async(Dispatchers.Default) {
val dao = KataomoiApplication.db.userEntityDao()
val list = dao.getAll(idStr)
items.postValue(list)
}
※DB操作はUIスレッドで触ろうとすると、非同期で触れや!ってRoomが例外飛ばしてくるので注意。
最後に
自分でCreate文とか書かなくて良いし、順番さえ覚えればかんたんに導入できる!って感じかな。
マイグレーションで若干ハマるけど、それは別のお話で、、、。