Androidアプリ開発初級編(Room)
今回は永続化ライブラリの一つであるRoomを使ってSQLiteのDatabase処理を学習していきたいと思います。
■事前準備
アプリのbuild.gradleファイルを編集します。
dependencies { .... implementation "androidx.room:room-runtime:2.2.6" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" kapt "androidx.room:room-compiler:2.2.6" .... }
■サンプルアプリの動作
計算処理結果を保存し、履歴を表示します
■Room処理の実装
まず、保存データを作成します。
Entityアノテーションを付けて保存テーブル名を記載します。
プライマリキーはRoomの自動生成機能を使用し、一意となるようにLong値のautoGenerate = trueを指定。
その他はColumnInfoアノテーションを使用し、それぞれカラム名を設定していきます。
@Entity(tableName = "calc_result_table") data class CalcResult( @PrimaryKey(autoGenerate = true) var calcResultId: Long = 0L, @ColumnInfo(name = "first_value") var firstValue: Int = 0, @ColumnInfo(name = "second_value") var secondValue: Int = 0, @ColumnInfo(name = "calc_sign") var calcSign: Int = 0, @ColumnInfo(name = "result_value") var resultValue: Int = 0 )
次にDatabase本体を定義していきます。
@Database(entities = [CalcResult::class], version = 1, exportSchema = false) abstract class CalcResultDatabase : RoomDatabase() { abstract val calcResultDatabaseDao: CalcResultDatabaseDao companion object { @Volatile private var INSTANCE: CalcResultDatabase? = null fun getInstance(context: Context): CalcResultDatabase { synchronized(this) { var instance = INSTANCE if (instance == null) { instance = Room.databaseBuilder( context.applicationContext, CalcResultDatabase::class.java, "calc_result_database" ).fallbackToDestructiveMigration().build() INSTANCE = instance } return instance } } } }
Databaseアノテーションを使用して、Entityやデータベースのバージョンを指定します。exportSchemaはスキーマをフォルダにエクスポートするかどうかを決めるものです。今回はバージョン管理の説明は省略するためfalseを指定しています。
companion object 、Volatileを使ったsingletonパターンで記述していきます。
データベースは複数のスレッドから呼び出されても問題のないように同期処理を行います。あとはRoom.databaseBuilderを使用して作成して完了です。migrationについてはバージョン管理については省略しますのでこの章では割愛します。
次にデータベース操作部分のDaoについて見ていきましょう。
@Dao interface CalcResultDatabaseDao { @Insert fun insert(result: CalcResult) @Update fun update(result: CalcResult) @Query("SELECT * FROM calc_result_table ORDER BY calcResultId ASC") fun getAllResults(): LiveData<List<CalcResult?>> @Query("DELETE FROM calc_result_table") fun clear() }
使用するQueryに合わせて様々な定義が出来ますので、任意のSQL文を書いてみてください。
次にFragment、ViewModel側を少し見てみましょう。
//Fragment private val viewModel: SelectNumberViewModel by viewModels { SelectNumberViewModelFactory(CalcResultDatabase.getInstance(requireActivity().application)) }
//ViewModel private val viewModelJob = Job() private var uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) private val calcResults = database.getAllResults() val clearEnabled = Transformations.map(calcResults) { it?.isNotEmpty() } val calcResultText = Transformations.map(calcResults) { formatCalcResult(it) } fun onClearButton() { uiScope.launch { clear() } } private suspend fun clear() { withContext(Dispatchers.IO) { database.clear() } } override fun onCleared() { super.onCleared() viewModelJob.cancel() } private fun formatCalcResult(results: List<CalcResult?>): String { var resultString = "" for (result in results) { result?.let { val sign = CalcSign.values()[result.calcSign] resultString += " ${result.firstValue} ${sign.getSign()} ${result.secondValue} = ${result.resultValue} \n" } } return resultString }
データベース操作はコルーチンを使用してUIスレッドをブロックしないようにしましょう。
データの保存内容に合わせて文字列を表示、履歴の有無に合わせてボタンのEnable状態が変更されるようにDataBindingとLiveDataを組み合わせています。
処理内容は以上となります。かなり簡易的な操作にしていますので、さらに大きなデータを扱うアプリなどを作成していろいろな操作を試してみてはいかがでしょうか。
■まとめ
・アノテーションを使用してEntity、Dao、Databaseを定義出来る
・Databaseのインスタンスはシングルトンパターンを使用する
・daoを使用したデータベース処理はUIスレッドをブロックしないように気を付けよう
DataBinding、LiveData、ViewModel、RoomとAACの基本部品を一通り使えるようになると、これらを組み合わせることである程度複雑なデータが絡むアプリも作れるようになるのではないかと思います。是非いろいろなアプリを作成してみてください。
それでは。