作成: 2020年09月22日
更新: 2021年02月01日
Androidアプリをタスクキルすると基本的にはデータはすべて消え初期状態になる.そうなっては困るデータは何らかの方法でファイルに保存する必要がある.Androidではそういった用途のためにSharedPreferencesとRoomがある.SharedPreferencesはxmlファイルに以下のように連想配列のような形式で書き込むことでデータを保持する.
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="boolean" value="true" />
<string name="string">hoge</string>
<set name="string-set">
<string>fuga</string>
<string>piyo</string>
</set>
<int name="int" value="0" />
</map>
比較的データの書き込み,読み込みが簡単でアプリの設定の値などを保存するには優れているが可変長配列のような複雑な形式には向かない.複雑な形式のデータを保存するにはSQLiteに保存するRoomを使う必要があるがデータの書き込み,読み込みになかなか癖があるので備忘録として残す.
Roomの使用にはアプリのbuild.gradleに以下の依存関係が必要
build.gradledependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
Roomでは上記のように実際にデータベースとやり取りするDatabaseクラスとデータベースアクセスに使用するメソッドを格納するDAO(Data Access Objects)とデータの形式を記述したEntitiesの3つで構成される.
今回はアラームアプリで用いたローカル音楽ファイルのデータ保存のためのクラスを例にとる.
データベース
SoundDatabase.kt@Database(entities = [Sound::class], version = 1)
abstract class SoundDatabase : RoomDatabase() {
abstract fun soundDao(): SoundDao
companion object {
private const val dbName = "sabi_alarm_sounds.db"
private var instance: SoundDatabase? = null
fun getInstance(context: Context): SoundDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context, SoundDatabase::class.java, dbName)
.fallbackToDestructiveMigration()
.build()
}
return requireNotNull(instance)
}
}
}
データベースクラスではデータベースの名前(dbName)を設定してデータベースにアクセスするインスタンスを作成する.companion objectにすることでどこから呼び出しても同じインスタンスを返すようにしている.また初回だけ実際にインスタンスを生成し2回目以降は同じインスタンスを返すようにしている.データベースの構造を変更した際はversion = 1の部分を増加させる必要がある.
DAO
SoundDao.kt@Dao
interface SoundDao {
@Query("SELECT * FROM sounds")
suspend fun getAll(): List<Sound>
@Query("SELECT * FROM sounds WHERE id IN (:soundIds)")
suspend fun loadAllByIds(soundIds: IntArray): List<Sound>
@Insert
suspend fun insertAll(vararg sounds: Sound)
@Update
suspend fun update(vararg sounds: Sound)
@Delete
suspend fun delete(sound: Sound)
}
DAOインターフェースではSQLのクエリに相当する関数を宣言する.getAllではデータベースのデータを全て取り出し,insertAllでは引数のデータを全てデータベースに保存する.コルーチン内で使用するために各関数にsusupendをつけておく.
Entity
Sound.kt@Entity(tableName = "sounds")
data class Sound(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "file_name") val fileName: String,
@ColumnInfo(name = "string_uri") val stringUri: String
)
Entityクラスではデータベースに保存するデータの形式をdata classとして定義する.今回音楽ファイルのIDとファイル名とURIを保存する.@PrimaryKey(autoGenerate = true)とすることでIDが自動で生成される.
データベースの読み込み,書き込みは以下のようにする.
val db = SoundDatabase.getInstance(context)
val dao = db.soundDao()
// 読み込み
CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.Main) {
val sounds = soundDao.getAll()
}
}
// 書き込み
CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.Main) {
soundDao.insertAll(sound)
}
}
データベース操作は基本的にメインスレッド以外で非同期処理で行う必要があるのでコルーチンを用いて操作している.