LoginSignup
1
0

More than 1 year has passed since last update.

Roomの使い方公式チュートリアルをやったメモ

Last updated at Posted at 2022-03-21

Google公式のチュートリアル

Room とフローの概要
2画面で構成され、最初の画面に内部DBから取得したバスの時刻表(バス停名・時刻)をRecycelerViewで表示。タップするとそのバス停だけの時刻表だけを表示する。

追記:
Room を使用してデータを永続化する
こっちのチュートリアルの方が詳しかった。

流れ

  • Entity用意
  • Dao用意(interface)。引数をFlow型にしておくとDBのデータ更新に応じてViewModel内データおよびUIも更新される
  • Databaseクラス用意(RoomDatabaseを継承。利用するEntityの指定やDaoに他のクラスからアクセスできるようなメソッド定義)
  • Applicationクラスを継承したクラスを用意(Databaseのインスタンスをこのクラスで行う)。AndroidManifest.xmlのタグのname属性をこのクラス名で更新する。アプリ起動時にDatabaseインスタンスが生成される?
  • ViewModelのメソッドでDaoのメソッドを扱えるようにする。ViewModelをDaoを引数としてインスタンス化できるようにViewModelProvider.Factoryなるものを定義する。Fragment内でViewModelの参照を取得する際は、このFactoryクラスを使う。

Entity

Dataクラスを用意。

Schedule.kt
/*
* バスのスケジュールのEntity(Roomが手キーやカラム名を識別するアノテーションを記載)
* */
@Entity
data class Schedule(
    @PrimaryKey val id: Int,

    @NonNull
    @ColumnInfo(name = "stop_name")
    val stopName: String,

    @NonNull
    @ColumnInfo(name = "arrival_time")
    val arrivalTime: Int
)

Dao

selectやinsert などSQLに応じたアノテーションを記載。メソッドの引数はFlowを指定。DB内のリストが変更になったら自動でUIも更新される。アプリ完成後、試しに[View]->[Tool Window]->[App Inspection]->[DataBase]でINSERT文発行するとリストが更新される。MyBatisっぽい。interfaceだけど、Roomがコンパイル時にこのクラスの実装を生成する。

ScheduleDao.kt
@Dao
interface ScheduleDao {
    // バス停と時刻表をすべて取得(フローを使用して、DBの更新をすぐUIに反映)
    @Query("SELECT * FROM schedule ORDER BY arrival_time ASC")
    fun getAll(): Flow<List<Schedule>>

    // バス停名で時刻表を取得(フローを使用して、DBの更新をすぐUIに反映)
    @Query("SELECT * FROM schedule WHERE stop_name = :stopName ORDER BY arrival_time ASC")
    fun getByStopName(stopName: String): Flow<List<Schedule>>
}

Databaseクラス

RoomDatabaseクラスを継承したDataBaseクラスを用意。使用するエンティティを Room に伝え、DAO にアクセスできるようにし、データベースの作成時のセットアップを行う。今回は、asettesフォルダ配下のbus_schedule.dbを読み込んで初期化している。Entityが増えたら、@Database内のentitiesに追加。versionはカラムが増えたりEntityが増えたりしたら上げていく。

AppDatabase.kt
/*
* モデル、DAO、データベースのセットアップを行うクラス
* */
@Database(entities = arrayOf(Schedule::class), version = 1)
abstract class AppDatabase : RoomDatabase() {

    /*
    * Daoを返す(他のクラスがDaoにアクセスできるように)
    * */
    abstract fun scheduleDao(): ScheduleDao

    /*
    * 既存のAppDatabaseインスタンスを返すメソッド、または初回データベースを作成
    * */
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    "app_database"
                )
                    .createFromAsset("database/bus_schedule.db")
                    .build()
                INSTANCE = instance

                instance
            }
        }
    }
}

Applicationクラス

Applicationクラスを継承したクラスを用意し、databaseのインスタンスをこのクラスで行う。このdatabaseをDatabaseクラスのgetDatabaseメソッドで初期化。ViewModelのコンストラクタに、このdatabaseを渡して、Daoのメソッドを利用する。AndroidManifest.xmlのapplicationタグのname属性を下記にしておく。よくわかないがアプリ起動時にデータベースオブジェクトが使えるようになる??

BusScheduleApplication.kt
class BusScheduleApplication : Application() {
    val database: AppDatabase by lazy { AppDatabase.getDatabase(this) }
}

ViewModel

コンストラクタ引数があるViewModelは直接インスタンス化できないらしい。そのため、ViewModelProvider.Factoryなるものでインスタンス化を行う。引数DaoのメソッドがViewModelのメソッドとして扱えるようになっている。フラグメントではViewModelのメソッド経由でDaoを利用する。

BusScheduleViewModel.kt
class BusScheduleViewModel(private val scheduleDao: ScheduleDao) : ViewModel() {

    fun fullSchedule(): Flow<List<Schedule>> = scheduleDao.getAll()

    fun scheduleForStropName(name: String): Flow<List<Schedule>> = scheduleDao.getByStopName(name)
}

/*
* ビューモデルをインスタンス化するファクトリークラス
* */
class BusScheduleViewModelFactory(private val scheduleDao: ScheduleDao) :
    ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(BusScheduleViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return BusScheduleViewModel(scheduleDao) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

FragmentでのViewModelの参照とUIへのデータ表示

下記のようにViewModelへの参照を設定。Applicationクラスを継承したクラス内でのdatabaseフィールドによりviewModel経由で、Daoのメソッドを利用しUIにデータを反映する。DBのデータ取得はコルーチンで行う。UIスレッドで行うとアプリが落ちる。

FullScheduleFragment.kt
// ビューモデルへの参照を取得
private val viewModel: BusScheduleViewModel by activityViewModels {
    BusScheduleViewModelFactory((activity?.application as BusScheduleApplication).database.scheduleDao())
}

// 中略

// viewModelの値を表示
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerView = binding.recyclerView
    recyclerView.layoutManager = LinearLayoutManager(requireContext())

    // リストタップしたときの処理(タップしたバス停名の時間を表示するフラグメントに遷移)
    val busStopAdapter = BusStopAdapter {
        val action =
            FullScheduleFragmentDirections.actionFullScheduleFragmentToStopScheduleFragment(
                stopName = it.stopName
            )
        view.findNavController().navigate(action)
    }
    recyclerView.adapter = busStopAdapter

    // submitList()でDBアクセスする。UIをロックしないようにコルーチンで行う。
    lifecycle.coroutineScope.launch {
        viewModel.fullSchedule().collect {
            busStopAdapter.submitList(it)
        }
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0