RoomのDaoからwithTransactionしやすくする
Roomのsuspend関数とwithTransaction
AndroidX RoomのDAOで定義したsuspend関数の実行は RoomDatabase
クラスの拡張関数として定義されている withTransaction
ブロック内で実行することが推奨されています。
正しくは「withTransaction
ブロック内で呼び出されるDAO関数はすべてサスペンド関数であることが推奨されている」でした。
なのでこの記事では単にDAOから RoomDatabase
に簡単にアクセスするための方法を紹介する記事になります ><
It is recommended that all Dao function invoked within the block be suspending functions.
問題点
DAOは以下のようにインターフェースで定義している場合が多いと思います。
@Dao
interface UserDao {
@Insert
suspend fun insertAll(vararg users: User)
@Delete
suspend fun delete(user: User)
@Query("SELECT * FROM user")
suspend fun getAll(): List<User>
}
RoomDatabase
とDAOを同じ場所で扱える場合は特に問題はありませんが、DIなどでDAOのみを渡しているようなクラスでは RoomDatabase
にアクセスすることができません。
この場合、DAOの代わりに RoomDatabase
を直接渡して利用側でDAOを取得するなどして解決できますが、本記事ではDAOから RoomDatabase
にアクセスする方法を考えていきます。
RoomDatabase
にアクセスする
DAOから Dao
アノテーションのドキュメントには以下の記述があります。
An abstract
@Dao
class can optionally have a constructor that takes a Database as its only parameter.
インターフェースではなく抽象クラスでDAOを定義する場合、 Database
をコンストラクトパラメータにすることが可能なようです。
次のようにDAOクラスを書き換えてみます。Databaseクラスに変更は必要ありません。
@Dao
abstract class UserDao(val database: RoomDatabase) {
@Insert
abstract suspend fun insertAll(vararg users: User)
@Delete
abstract suspend fun delete(user: User)
@Query("SELECT * FROM user")
abstract suspend fun getAll(): List<User>
}
こうすることによって、利用側ではDAOから RoomDatabase
を取得できるようになりました。
dao.database.withTransaction {
dao.getAll()
}
withTransaction
の呼び出しを簡素化する
先述の例だと dao
の呼び出しがくどいのでDAOクラスに関数を追加します。
@Dao
abstract class UserDao(val database: RoomDatabase) {
// 省略..
suspend fun <R> withTransaction(block: suspend UserDao.() -> R): R {
return database.withTransaction {
block(this)
}
}
}
DAOクラスに withTransaction
関数を追加することで呼び出し側がだいぶ簡素になります。
dao.withTransaction {
getAll()
}
しかし、これだとDAOクラス全てに withTransaction
関数を追加していく必要がありちょっとつらいので次のようなインターフェースと拡張関数を定義します。
internal interface Transactionable {
val database: RoomDatabase
}
suspend fun <R, T : Transactionable> T.withTransaction(block: suspend T.() -> R): R {
return database.withTransaction {
block(this)
}
}
あとは、DAOクラスに先程追加した関数を削除して Transactionable
インターフェースを継承する形にすればOK
@Dao
abstract class UserDao(override val database: RoomDatabase) : Transactionable {
@Insert
abstract suspend fun insertAll(vararg users: User)
@Delete
abstract suspend fun delete(user: User)
@Query("SELECT * FROM user")
abstract suspend fun getAll(): List<User>
}
Discussion