#はじめに
この記事では、軽くRoomに触れつつ実際の使い方を書いています。
ゆるめの理解でとりあえず使いたい!という人向けです。
#Roomって何?
Androidで内部データベースを扱いたい場合は、SQLiteを一般的には使います。
ですが、このSQLiteをそのまま使うことは現在非推奨になっています。理由としてはこちらの公式ドキュメントによると『クエリはコンパイル時に検証されない』『大量のボイラープレートコードが必要になる』等とのことです。
Roomは、このSQLiteをスマートに使うための物です。
Roomは3つの要素から構成されます。
###Entity
データベースに入れるアイテムの事です。
基本的にはkotlinのdata classを使用します。
###Dao(データアクセスオブジェクト)
データベースに、Entityに関するクエリを与えることが出来るインターフェースです。
ここによく使うクエリを登録します。
###データベース
読んで字のごとく、データベースです。
この中に使用するEntityとDaoを登録します。
#使い方
Roomを使う場合、androidxのRoom依存関係をbuild.gradle(app)に記述します。(projectのGradle Scriptの所にあります)
また、Roomを使うのにkaptという物も必要になるので、プログラムの先頭にapply plugin: "kotlin-kapt"
と記述します。
次に、以下をdependenciesの中に記述します。
dependencies{
//..(省略)
def room_version = "2.2.4"
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 - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
}
上記のdef room_version = "2.2.4"
の部分は、こちらに最新のバージョンがありますので、その時必要に応じて更新して下さい。わざわざ公式ドキュメントを見なくてもIDEなら警告と思うので多分気が付きます。(日本語版だと古いバージョンのままのときがあります。)
次に、先程の三要素を作っていきます。
Entity
@Entity
data class HumanEntity constructor(
@PrimaryKey(autoGenerate = true) val id: Int,
val name: String,
val age: Int
)
見ての通りで、これは人間のEntityです。idがPrimariyKeyで、他の複数のデータと重複しないものです。また、autoGenerate = true
とすると、idをこちらから設定しなくても適当に割り振ってくれます。今回はこれを出し入れします。
###Dao
@Dao
interface UserDao{
//データベース上の全てのHumanEntityを取得
@Query("SELECT * FROM HumanEntity")
//データベース上にあるhumanNameさんを全て取得
fun getHumanAll(): List<HumanEntity>
@Query("SELECT * FROM HumanEntity WHERE name == :humanName")
fun searchName(humanName: String): List<HumanEntity>
//humansを全て取得、primaryKey重複の際には入れ替え
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertHuman(vararg humans: HumanEntity)
//humansを全て消去
@Delete
fun deleteAllHuman(vararg humans: HumanEntity)
}
このように、@Query
を使ってSQLiteのクエリを作ったり、@Insert
や@Delete
を使って挿入したり削除したりのクエリを作成することが出来ます。
もしクエリが間違っていれば、ここでエラーを吐いてくれます。
その他にも使えるものはあるので、こちらを参照して下さい。
###データベース
@Database(entities = [HumanEntity::class],version = 1)
abstract class UserDataBase: RoomDatabase(){
abstract fun userDao(): UserDao
}
他のEntityを追加したい場合は、[]の中に加えてあげます。
Daoを追加したい場合も、abstruct fun 〇〇Dao(): 〇〇Dao
と同じように追加します。
それでは、これらを使うプログラムを実際に記述していきます。
機能としては
- データベース内の全ての人間を表示
- データベースに人間を登録する
- データベース内の名前が〇〇である人を全て削除
を作ります。
今回は、TableLayoutとTableRowを使用します。
また、分かりやすくするため、xml内の設定を直打ちしています。(実際にはちゃんとresourceを使ったほうが良いです)
以下コードです。少し長いです。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TableLayout
android:background="#AAFFFF"
android:id="@+id/humanTable"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.8">
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/headRow">
<TextView
android:layout_height="wrap_content"
android:layout_width="200dp"
android:text="名前"/>
<TextView
android:layout_height="wrap_content"
android:layout_width="200dp"
android:text="年齢"/>
</TableRow>
</TableLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:layout_weight="0.2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="20dp">
<EditText
android:id="@+id/addNameInput"
android:layout_width="150dp"
android:layout_height="match_parent"
android:inputType="text"
android:hint="名前"/>
<EditText
android:id="@+id/addAgeInput"
android:layout_width="150dp"
android:layout_height="match_parent"
android:inputType="number"
android:hint="年齢"/>
<Button
android:id="@+id/addButton"
android:layout_width="100dp"
android:layout_height="match_parent"
android:text="追加"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/removeNameInput"
android:layout_width="300dp"
android:layout_height="match_parent"
android:inputType="text"
android:hint="名前"/>
<Button
android:id="@+id/removeButton"
android:layout_width="100dp"
android:layout_height="match_parent"
android:text="削除"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
これは、app/res/layout にlayout resources fileとして作って下さい。
<?xml version="1.0" encoding="utf-8"?>
<TableRow
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/nameTextView"
android:layout_height="wrap_content"
android:layout_width="200dp"
android:text="名前"/>
<TextView
android:id="@+id/ageTextView"
android:layout_height="wrap_content"
android:layout_width="200dp"
android:text="年齢"/>
</TableRow>
class MainActivity : AppCompatActivity() {
private lateinit var db: UserDataBase
private lateinit var dao: UserDao
private val mainHandler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//database という名前のデータベースを使用
db = Room.databaseBuilder(
applicationContext,
UserDataBase::class.java,"database"
).build()
dao = db.userDao()
setAllHumanToTable()
addButton.setOnClickListener(ButtonListener())
removeButton.setOnClickListener(ButtonListener())
}
//データベース内にあるデータを全て表示します
private fun setAllHumanToTable(){
//データベース関連の動作は重いため、メインスレッドで動かしてはいけない決まりがあります
AsyncTask.execute{
val humanList = dao.getHumanAll()
for(human in humanList){
//全てのhumanをrowに追加
val row = View.inflate(this@MainActivity,R.layout.human_row,null)
row.nameTextView.text = human.name
row.ageTextView.text = human.age.toString()
//逆に、メインスレッド以外でUIの操作は出来ません
//なので、このようにしてメインスレッドにViewの追加をお願いしています
mainHandler.post{
humanTable.addView(row)
}
}
}
}
private fun resetTable(){
//humanTableの子を全て参照して
//先頭以外のrowを全て削除しています
humanTable.children.forEach {
if(it is TableRow && it.id != R.id.headRow){
mainHandler.post{
humanTable.removeView(it)
}
}
}
setAllHumanToTable()
}
private fun addHumanFunction(){
AsyncTask.execute{
val name = addNameInput.text.toString()
val age = addAgeInput.text.toString().toIntOrNull() ?: -1
if(name != "" && age >= 0){
dao.insertHuman(HumanEntity(id = 0,name = name,age = age))
}
}
}
private fun removeHumanFunction(){
AsyncTask.execute{
val name = removeNameInput.text.toString()
val humanList = dao.searchName(name)
for(human in humanList){
dao.deleteAllHuman(human)
}
}
}
inner class ButtonListener: View.OnClickListener{
override fun onClick(v: View) {
AsyncTask.execute {
when(v.id){
R.id.addButton -> addHumanFunction()
R.id.removeButton -> removeHumanFunction()
}
resetTable()
}
}
}
}
ソースコードに書いてある部分もありますが、注意点があります。
###メインスレッドでデータベース操作は出来ない
データベースの処理は重いので、メインスレッドで実行してはいけない決まりがあります。もし、メインスレッドで実行してしまうと実行時にエラーが発生します。
なので、非同期処理をするようにしましょう。今回の用にAsyncTask.excute()
を使うのはあくまで一例です。
###DataBaseオブジェクトを何個も作らない
先程のこちらにも書いてありますが、データベースオブジェクトは重いので、シングルトン設計パターンに従うようにして下さい。
#おまけ
今回のコード等はこちらにあります。
#参考
https://qiita.com/yukiyamadajp/items/73bffb6a3697cb62f9e1
https://qiita.com/b_a_a_d_o/items/45bda89f49bf163144af
https://developer.android.com/training/data-storage/room?hl=ja
https://developer.android.com/training/data-storage/room/accessing-data?hl=ja