エンジニア男

勉強したことの忘備録としてつらつら書いていきます。

【Android】Roomの使い方

Roomの使い方についてメモ。

 

・公式ドキュメント

https://developer.android.com/training/data-storage/room

・コードラボ

https://codelabs.developers.google.com/codelabs/android-training-livedata-viewmodel/index.html?index=..%2F..android-training#0

GitHub

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を使用する