Room
今回は Kotlin + Room + Coroutines + Koin
の構成で進めていきたいと思います。
構成要素

※公式ページから引用
以下の3つの要素で構成されている
・Database: データベース内のエンティティとデータアクセスオブジェクトのリストを定義
・Entity: 各エンティティに対して、アイテムを保持するためのデータベーステーブルが作成される
・Dao: データベースにアクセスするメソッドを定義する役割を果たす
環境構築
必要なパッケージをインストールします。
def koin_version = '1.0.2'
implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
def coroutines_version = '1.2.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
// viewmodel + coroutines
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha02"
def room_version = "2.1.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
実装
簡単なサンプルとしてメッセージを扱う構成のDatabaseを実装していきます。
Entity作成
まずメッセージの所有者を扱うユーザー管理用のEntityを作成します。
@Entity
data class UserData(
@PrimaryKey(autoGenerate = true)
val id: Long,
val name: String
)
id
に関しては @PrimaryKey(autoGenerate = true)
で主キーかつ
自動採番するように設定しています。
次ににメッセージを保存するEntityを作成
@Entity(foreignKeys = [ForeignKey(
entity = UserData::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("owner_id")
)])
data class MessageData(
@PrimaryKey(autoGenerate = true)
val id: Long,
@ColumnInfo(name = "owner_id")
val ownerId: Long,
val body: String,
val type: Int,
@ColumnInfo(name = "is_owner")
val isOwner: Boolean,
@ColumnInfo(name = "is_delete")
val isDelete: Boolean,
@ColumnInfo(name = "created_at")
val createdAt: Date
)
ForeignKey
で外部キーをowner_id
に設定し、
1つ前に作成した UserData
の id
と紐づけています。
@ColumnInfo(name = "xxxxx")
では名前が実際の列名と異なる場合に設定しています。
Converter作成
Entityの MessageData
で Date
型を使用していますが、
実際のDBデータにはDate型は無い為、変換が必要になります。
class DateConverter {
@TypeConverter
fun fromTimestamp(value: Long?): Date?
= value?.let { Date(it) }
@TypeConverter
fun dateToTimestamp(date: Date?): Long?
= date?.let { it.time }
}
上記では、Date型をLongの値へ変換しDBデータとして格納するように変換しています。
Dao作成
ユーザーを検索したり登録するDaoを作成します。
@Dao
interface UserDao {
// 登録されている全ユーザーを取得
@Query("SELECT * FROM UserData")
suspend fun findAll(): List<UserData>
// userIdで指定したユーザーを検索
@Query("SELECT * FROM UserData WHERE id = :userId")
suspend fun find(userId: Long): UserData
// ユーザー登録
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun add(userData: UserData)
}
メッセージ一覧の取得や、登録を行うDaoを作成
@Dao
interface MessageDao {
// メッセージ登録
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun add(messageData: MessageData)
// 全メッセージ取得
@Query("SELECT * FROM MessageData")
suspend fun findAll(): List<MessageData>
}
Database作成
@androidx.room.Database(entities = [
MessageData::class,
UserData::class
], version = 1)
@TypeConverters(DateConverter::class)
abstract class Database: RoomDatabase() {
abstract fun messageDao(): MessageDao
abstract fun userDao(): UserDao
}
EntitiyとConverter、Dao生成メソッドを定義しています。
Koinを使用してDI
object KoinModule {
fun appModule() = module {
// Databaseインスタンスをシングルトンで生成
single { Room.databaseBuilder(androidContext(), Database::class.java, "xxx.db").build() }
}
fun messageModule() = module {
// Daoを生成
factory { get<Database>().messageDao() }
// viewModelのinjection
viewModel { MessageViewModel(get()) }
// repositoryのinjection
single { MessageRepository(get()) }
}
}
使用する
- MessageRepository
class MessageRepository(private val messageDao: MessageDao) {
/**
* Query all messages.
*/
suspend fun getMessages(): List<MessageData>
= messageDao.findAll()
}
- ViewModel
class MessageViewModel(private val messageRepository: MessageRepository): ViewModel() {
private var messageList = MutableLiveData<List<MessageData>>()
/**
* LiveData取得
*
* @return {@link LiveData}
*/
fun getMessagesLiveData(): LiveData<List<MessageData>> = messageList
/**
* List更新
*/
fun fetchList() {
viewModelScope.launch {
messageList.value = messageRepository.getMessages()
}
}
}
バッドノウハウ
- ビルド時に以下のエラーが発生
org.koin.error.BeanInstanceCreationException: Can't create definition for 'Factory [name='ListViewModel',class='xxxxxxxxxxxx.ui.ListViewModel']' due to error :
Can't create definition for 'Factory [name='MessageBoxesRepository',class='xxxxxxxxxxxx.repository.MessageBoxesRepository']' due to error :
Can't create definition for 'Factory [name='MessageDao',class='xxxxxxxxxxxx.repository.db.dao.MessageDao']' due to error :
Can't create definition for 'Single [name='Database',class='xxxxxxxxxxxx.repository.db.Database']' due to error :
cannot find implementation for xxxxxxxxxxxx.repository.db.Database. Database_Impl does not exist
....
kapt
としないといけない所が annotationProcessor
になっていないか要確認
apply plugin: 'kotlin-kapt'
...
kapt "androidx.room:room-compiler:$room_version"
- ビルド時に以下のエラーが発生
Maybe you forgot to add the referenced entity in the entities list of the @Database annotation?
@ForeignKey
を使った時に↑のエラーに遭遇
エラーメッセージ通り、@Database
アノテーションにEntityを追加するのを忘れていただけ...