Roomの使い方についてメモ。
・公式ドキュメント
https://developer.android.com/training/data-storage/room
・コードラボ
https://github.com/shota-suzuki06/android_word_book_app
コードラボのRoomのチュートリアルをまとめ。
Room実装手順
1, Gradleファイルの更新
build.gradle(Module: app)ファイルに下部を追加する。
apply plugin: 'kotlin-kapt'
dependencies {
// Room components
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
implemantation "androidx.room:room-ktx:2.2.5"
androidTestImplementation "androidx.room:room-testing:2.2.5"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
}
2, Entityの作成
@Entity(tableName = "word_table")
data class Word(
@Primarykey(autoGenerate = true)
val id: Int,
val word: String
)
@Entity クラスは、DBテーブル内のEntityを表す。
3, DAOの作成
@Dao
interface WordDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertWord(word: Word)
@Query("SELECT * FROM word_table ORDER BY id ASC")
fun getAllData(): LiveData<ListView>
@Query("DELETE FROM word_table")
suspend fun deleteAllWords()
}
RoomはDAOを使用して、DBにクエリを発行する。
DAOはinterface または abstract クラスで作成する。
4, LiveDataとは
LiveDataは、データ監視用のライフサイクルライブラリクラスであり、アプリがデータの変更に対応するのに役立つ。
LiveData<型指定> と記述すると、Roomはデータベースが更新された時に、更新に必要な全てのコードを生成する。
5, Room DataBaseの作成
@Database(entities = [Word::class], version = 1, exportSchema = false)
abstract class WordDatabase: RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
@Volatile
private var INSTANCE: WordDatabase? = null
fun getDatabase(context: Context): WordDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
Roomは、SQLiteデータベースの上にあるデータベースレイヤー。
RoomDatabaseを継承し、RoomDatabaseを実装する。
6, Repositoryの作成
class WordRepository(private val wordDao: WordDao) {
val readAllData: LiveData<List<Word>> = wordDao.getAllData()
suspend fun insertWord(word: Word) {
wordDao.insertWord(word)
}
suspend fun deleteAllWords() {
wordDao.deleteAllWords()
}
}
Repositoryは、複数のデータ・ソースへのアクセスを抽象化するクラス。
具体的に言うと、ネットワークからデータをfetchするか、ローカルDBのDB操作を行う。
7, ViewModelの作成
class WordViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<Word>>
private val repository: WordRepository
init {
val wordDao = WordDatabase.getDatabase(application).wordDao()
repository = WordRepository(wordDao)
readAllData = repository.readAllData
}
fun insertWord(word: Word) {
viewModelScope.launch(Dispatchers.IO) {
repository.insertWord(word)
}
}
fun deleteAllWords() {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteAllWords()
}
}
}
ViewModelの役割はUIにデータを提供し、構成の変更を維持するためのクラス。
Activity/Fragmentから呼び出し、DB操作を行う。
8, Adapterの作成
class ListAdapter: RecyclerView.Adapter<ListAdapter.WordViewHolder>() {
private var wordList = emptyList<Word>()
class WordViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
return WordViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item, parent, false))
}
override fun getItemCount(): Int {
return wordList.size
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val currentItem = wordList[position]
holder.itemView.textView.text = currentItem.word.toString()
}
fun setData(word: List<Word>) {
this.wordList = word
notifyDataSetChanged()
}
}
RecyclerView.Adapterを継承し、RecyclerViewにデータをsetしている。
DBから取得した値をAdapterにsetし、UI上に表示する
9, UI(Activity/Fragment)からRoomを操作する
class FirstFragment : Fragment() {
// viewModelを格納する変数
private lateinit var mWordViewModel: WordViewModel
override fun createView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_first, container, false)
val adapter = ListAdapter()
val recyclerView = view.recyclerview
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
val helper = ItemTouchHelper(object: ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView.ViewHolder, direction: Int) {
return false;
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
val word = adapter.getData(position)
Toast.makeText(requireContext(), word.word + " を削除しました", Toast.LENGTH_SHORT).show()
mWordViewModel.deleteWord(word)
}
})
helper.attachToRecyclerView(recyclerView)
// UIをデータに接続する
mWordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
mWordViewModel.readAllData.observe(viewLifecycleOwner, Observer { word ->
adapter.setData(word)
})
view.fab.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
setHasOptionsMenu(true)
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_main, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_delete) {
deleteAllWords()
}
return super.onOptionsItemSelected(item)
}
private fun deleteAllWords() {
val builder = AlertDialog.Builder(requireContext())
builder.setPositiveButton("Yes") { _, _ ->
mWordViewModel.deleteAllWords()
Toast.makeText(requireContext(), "単語を全て削除しました", Toast.LENGTH_SHORT).show()
}
builder.setNegativeButton("No") { _, _ -> }
builder.setTitle("単語を全て削除しますか?")
builder.setMessage("単語を削除したら戻すことはできません。よろしいですか?")
builder.create.show()
}
}
UIにてDBからデータを取得する手順
1, viewModelから値を取得
2, UIにデータを渡す
3, DB操作を行う際はviewModelを使用する