Roomを利用してデータを保存・操作する方法をまとめています。
また、アプリ上でDB操作を行う場合は非同期処理で実装する必要がありますので、RxJavaを利用する方法を紹介しています。
Roomについて
概要
Androidアプリで使用するDBは「SQLite」です。 「SQLite」 を直接使用してDB操作を行うことも可能ですが、「Room」ライブラリを使用することで実装が容易になります。
また、公式でも 「Room」ライブラリ を使用することが推奨されています。
構成と役割
3つのコンポーネントから構成されています。
アノテーション(@~)を使用すれば、簡単に実装できるようになっています。
- Database
- Entity
- DAO
Database
@Databaseアノテーションを使用します。
RoomDatabaseクラスを継承して実装します。
DB操作を行うためのインスタンスを作成することが役割です。
Entity
@Entityアノテーションを使用します。
データ構造を示す、data classです。
カラムの型や名称、主キーの設定もここでします。
DAO
@DAOアノテーションを使用します。
DBを操作するためのメソッドやクエリをinterfaceで定義します。
実装例
実際にやってみるのが手っ取り早いので、実装例を示していきます。
画面上にテキストを入力して、ボタンを押すと登録を行うようなアプリを作っていきます。
レイアウト
以下のような画面を作成します。
上から順番にPlain Text、Button、List Viewの3つです。
- Plain Text
文字を入力するテキストボックス - Button
DBへ登録するトリガー - List View
DBに登録されている内容を表示する
処理概要
- テキスト入力
- 登録する
- リストビューを更新
実装方法
build.gradle(:app)
大きく分けると、以下3つが必要になります。
- room
DB操作に必要 - kapt
アノテーションを使うために必要 - rxjava
非同期処理実装に必要
pluginにkaptを追加
plugins { … id 'kotlin-kapt' }
各種ライブラリを追加
//room
implementation 'androidx.room:room-runtime:2.3.0'
//kapt
kapt 'androidx.room:room-compiler:2.3.0'
//rxjava
implementation 'androidx.room:room-rxjava2:2.3.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.4'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
User.kt
@Entity
data class User(
@PrimaryKey(autoGenerate = true) val uid: Int,
@ColumnInfo(name = "name") val name: String?
)
Userテーブルには、uidとnameが含まれているというデータ構造を示しています。
@PrimaryKey(autoGenerate = true) val uid: Int,
@PrimaryKeyは主キー、 autoGenerateは自動採番を表しています。この場合、データを登録するときに、uidを未指定でも自動で1から順番に番号が振られるようになります。
@ColumnInfo(name = "name") val name: String?
@ColumnInfoは別名を付ける場合に使用します。
UserDao.kt
@Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flowable<List<User>> @Insert fun insertAll(user: User) @Delete fun delete(user: User) }
@Query("SELECT * FROM user") fun getAll(): Flowable<List<User>>
@Queryアノテーションは任意のクエリを書きます。Userテーブルのデータを取得した結果を返却するメソッドとなっています。FlowableはRxJavaによる非同期処理を実装するために必要です。
@Insert fun insertAll(user: User)
@Insertはデータを登録するためのアノテーションです。引数でUserデータを渡して実行します。
@Delete fun delete(user: User)
@Deleteはデータを削除するためのアノテーションです。引数でUserデータを渡して実行します。
AppDatabase.kt
@Database(entities = [User::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "user_database") .build() INSTANCE = instance // return instance instance } } } }
@Database(entities = [User::class], version = 1, exportSchema = false)
entitiesには、前もって作成したUserクラスを指定します。複数のEntityを使用する場合は、カンマ区切りで指定します。
abstract fun userDao(): UserDao
クエリを実行するためのDaoを定義しておきます。
fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "user_database") .build() INSTANCE = instance // return instance instance } }
Databaseのインスタンスを作成して返却します。 “user_database” はDBの名称です。
MainActivity.kt
class MainActivity : AppCompatActivity() { companion object { lateinit var editTextName: EditText lateinit var buttonUpdate: Button lateinit var listViewNames: ListView lateinit var db : AppDatabase lateinit var dao : UserDao } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //コンポーネント取得 editTextName = findViewById(R.id.editTextName) buttonUpdate = findViewById(R.id.buttonUpdate) listViewNames = findViewById(R.id.ListViewNames) //DB関連のインスタンス取得 db = getDatabase(this) dao = db.userDao() //リストの初期表示 getAllUser(this) //ボタンのリスナーを設定 buttonUpdate.setOnClickListener { //入力内容の登録 insUser(editTextName.text.toString()) editTextName.text.clear() //リストの再取得 getAllUser(this) } } //ListViewにデータを表示する private fun getAllUser(context : Context) { //ioスレッド:DBからデータ取得 //mainスレッド:取得結果をUIに表示 dao.getAll() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { //データ取得完了時の処理 val data = ArrayList<User>() it.forEach { user -> data.add(user) } //リスト項目とListViewを対応付けるArrayAdapterを用意する //リストで使用するlayout(simple_list_item_1)を指定する val adapter = ArrayAdapter( context, android.R.layout.simple_list_item_1, data) listViewNames.adapter = adapter } , { //エラー処理 } ) } //入力内容をDBに登録する private fun insUser(name : String) { Completable.fromAction { dao.insertAll(User(0, name)) } .subscribeOn(Schedulers.io()) .subscribe() } }
dao.getAll() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { //データ取得完了時の処理 } , { //エラー処理 } )
非同期でDB操作を実行します。
実行される順番と概要です。
- dao.getAll()
UserDaoで用意したUserテーブルのデータを取得するメソッド - .subscribeOn(Schedulers.io())
入出力スレッドでgetAll()を実行する - .observeOn(AndroidSchedulers.mainThread())
実行スレッドをメインスレッド(UIスレッド)に切り替える - .subscribe({},{})
入出力スレッドで実行した結果を利用して、さらに処理を実行する
DB操作は非同期で行わないと例外が発生します。また、DB操作はIOスレッド(入出力スレッド)で実行します。
今回はDBから取得した内容をアプリ画面上に表示しますので、途中でメインスレッド(UIスレッド)に切り替えています。 メインスレッド(UIスレッド) でないとUI操作は行えないようになっています。
まとめると、行う処理に合わせてスレッドを選択する必要があるということです。ひとまず、以下のように覚えておいて差し支えありません。
- IOスレッド(入出力スレッド)
DBやファイル操作といった入出力に関する処理を行います。こういった処理はパフォーマンスに影響するため、メインスレッドでは実行しないようにしましょう。 - メインスレッド(UIスレッド)
UI関連の処理を行います。特に明示しない限りはこのスレッドが使用されます。
Completable.fromAction { dao.insertAll(User(0, name)) } .subscribeOn(Schedulers.io()) .subscribe()
insert処理も同様に非同期で実装します。ただし、データ取得とは違いUIへの反映はしませんので、メインスレッドへの切り替えは必要ありません。
Room実装時に遭遇したエラーはこちらにまとめています。
実行結果
アプリ起動時
すでにいくつか登録済みなので、リストに表示されています。
テキストを入力・登録
「Test Taro」と入力して、登録ボタンを押します。
登録後
リストの一番下に「Test Taro」と表示されていますので、正常に登録が行えたことが分かります。
まとめ
要点を箇条書きでまとめます。
Roomについて
- DBの操作はRoomライブラリを利用すると実装が容易にできる
- DB操作を行うには非同期で実装する必要がある
- Roomによる実装を行うには以下の3つが必要である
- Database
RoomによるDB操作を実装
- Entity
データ構造 - DAO
DB操作を行う処理群
- Database
RxJavaについて
今回は、非同期処理を実装するためにRxJavaを利用する方法を紹介しました。
- 処理によってスレッドを使い分ける
- IOスレッド(入出力スレッド)
DBやファイル操作といった入出力に関する処理を行う - メインスレッド(UIスレッド)
UI関連の処理を行う
- IOスレッド(入出力スレッド)