Roomを利用したDBへのデータ保存・操作方法【Androidアプリ開発】

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に登録されている内容を表示する
レイアウト

処理概要

  1. テキスト入力
  2. 登録する
  3. リストビューを更新

実装方法

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操作を実行します。

実行される順番と概要です。

  1. dao.getAll()
    UserDaoで用意したUserテーブルのデータを取得するメソッド
  2. .subscribeOn(Schedulers.io())
    入出力スレッドでgetAll()を実行する
  3. .observeOn(AndroidSchedulers.mainThread())
    実行スレッドをメインスレッド(UIスレッド)に切り替える
  4. .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操作を行う処理群
RxJavaについて

今回は、非同期処理を実装するためにRxJavaを利用する方法を紹介しました。

  • 処理によってスレッドを使い分ける
    • IOスレッド(入出力スレッド)
      DBやファイル操作といった入出力に関する処理を行う
    • メインスレッド(UIスレッド)
      UI関連の処理を行う

参考

Room を使用してローカル データベースにデータを保存する|Android Developers

Android Room とビュー – Kotlin|Android Developers

Androidアプリ開発
この記事を書いた人

エンジニアとして仕事をしています。
仕事や趣味を通して、開発やプログラミングについて学んだことを綴っていきます。
 ・実務経験は、WEBシステムのサーバーサイドコーディングがメイン
 ・アプリ開発は趣味程度

akihiro-takedaをフォローする
akihiro-takedaをフォローする
プログラミング・開発の備忘録
タイトルとURLをコピーしました