はじめに
RoomとRecyclerViewを使った簡単なメモアプリを作成したので共有します。
RoomはSQLiteを活用したローカルデータベースであり、公式でも推奨されています。
参考:Room を使用してローカル データベースにデータを保存する
今回はメモの追加と編集、削除ができる簡単なアプリを作成しました。またAdapterにはListAdapterを採用しています。
ソースコードはこちらから
環境
- Kotlin 1.6.10
- Android Studio Bumblebee | 2021.1.1
- Room 2.2.2
- targetSDK: 32
概要
- Roomの初期設定
- View Bindingの設定
- Entityの作成
- Daoの作成
- RoomDatabaseの作成
- ListAdapterの作成
- Mainactivityで一覧表示
- EditActivityでメモの追加と編集、削除
1. Roomの初期設定
app/build.gradle の dependencies に以下を追加
def room_version = "2.2.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
app/build.gradle の plugins に以下を追加
id "org.jetbrains.kotlin.kapt"
Bumblubeeからライブラリの導入の仕方が変わったので注意しましょう。
2. View Bindingの設定
build.gradleのandroidに以下のコードを追加します
buildFeatures{
viewBinding true
}
MainActivity.ktに以下のコードを追加します
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater).apply { setContentView(this.root) }
}
}
これでView Bindingの設定は終わりです。
3. Entityの作成
data class Memo を作成しEntityを定義します
@Entity(tableName = "memos")
data class Memo(
@PrimaryKey(autoGenerate = true) val id: Int,
val content: String?
)
ここでデータベースのテーブルEntity を定義しています。idをPrimaryKeyとして設定し、また自動生成されるようにautoGenerrateをtrueにしています。
4. Daoの作成
interface MemoDao.ktを作成しDaoを定義します
@Dao
interface MemoDao {
@Insert
fun insert(memo : Memo)
@Update
fun update(memo : Memo)
@Delete
fun delete(memo : Memo)
@Query("delete from memos")
fun deleteAll()
@Query("select * from memos")
fun getAll(): List<Memo>
@Query("select * from memos where id = :id")
fun getMemo(id: Int): Memo
}
データベースにアクセスするためのインターフェイスMemoDaoを定義します。ここで後に使うメソッドを定義します。
5. RoomDatabaseの作成
MemoDatabase.ktを作成しDatabaseを定義します
@Database(entities = [Memo::class], version = 1)
abstract class MemoDatabase : RoomDatabase() {
abstract fun memoDao(): MemoDao
}
6. ListAdapterの作成
memo_itemの作成
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/memo_text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="Hello World!"
android:textColor="#000000"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:dividerInsetEnd="16dp"
app:dividerInsetStart="16dp"
app:layout_constraintTop_toBottomOf="@+id/memo_text_view"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecyclerViewに表示するmemo_itemを作成します。
MemoAdapterの作成
class MemoAdapter(private val onClickListener: OnClickListener): ListAdapter<Memo, MemoViewHolder>(diffUtilItemCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MemoViewHolder {
val view = MemoItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MemoViewHolder(view)
}
override fun onBindViewHolder(holder: MemoViewHolder, position: Int) {
val memo = getItem(position)
holder.bind(memo)
holder.itemView.setOnClickListener {
onClickListener.onClick(memo)
}
}
}
class MemoViewHolder(
private val binding: MemoItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(memo: Memo) {
binding.memoTextView.text = memo.content
}
}
private val diffUtilItemCallback = object : DiffUtil.ItemCallback<Memo>() {
override fun areContentsTheSame(oldItem: Memo, newItem: Memo): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Memo, newItem: Memo): Boolean {
return oldItem.id == newItem.id
}
}
class OnClickListener(val clickListener: (memo: Memo) -> Unit) {
fun onClick(memo: Memo) = clickListener(memo)
}
RecyclerViewAdapterではなくListAdapterを使用しています。
7. MainActivityで一覧表示
MainActivityを変更していきます
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@android:drawable/ic_menu_add" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater).apply { setContentView(this.root) }
//EditActivityは後で作成します
val toEditIntent = Intent(this,EditActivity::class.java)
//roomのインスタンス生成
val db = Room.databaseBuilder(
applicationContext,
MemoDatabase::class.java, "database-name"
).allowMainThreadQueries().build()
//Daoのインスタンス生成
val memoDao = db.memoDao()
//roomのデータを全て取得
val memos: List<Memo> = memoDao.getAll()
//OnClickListenerを引数としてMemoAdapterのインスタンス生成
val memoAdapter = MemoAdapter(
OnClickListener { memo ->
toEditIntent.putExtra("ID",memo.id)
startActivity(toEditIntent)
}
)
memoAdapter.submitList(memos)
binding.recyclerView.adapter = memoAdapter
binding.recyclerView.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.fab.setOnClickListener {
startActivity(toEditIntent)
}
}
}
7. EditActivityでメモの追加と編集、削除
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".EditActivity">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/edit_text"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:passwordToggleEnabled="false">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="メモを入力"
android:imeOptions="actionDone"
android:maxLines="1"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/save_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:layout_marginEnd="24dp"
android:text="登録"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edit_text" />
<Button
android:id="@+id/delete_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:layout_marginEnd="16dp"
android:text="削除"
app:layout_constraintEnd_toStartOf="@+id/submit_button"
app:layout_constraintTop_toBottomOf="@+id/edit_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
private lateinit var binding: ActivityEditBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEditBinding.inflate(layoutInflater).apply { setContentView(this.root) }
val db = Room.databaseBuilder(
applicationContext,
MemoDatabase::class.java, "database-name"
).allowMainThreadQueries().build()
val id:Int = intent.getIntExtra("ID",0)
val memoDao = db.memoDao()
val memo = memoDao.getMemo(id)
val toMainIntent = Intent(this, MainActivity::class.java)
if(id == 0) {
binding.deleteButton.isInvisible = true
binding.saveButton.setOnClickListener {
val memo = Memo(
id,
binding.textInput.text.toString()
)
memoDao.insert(memo)
startActivity(toMainIntent)
}
}else{
binding.deleteButton.isVisible = true
binding.textInput.setText(memo.content)
binding.saveButton.setOnClickListener {
val memo = Memo(
id,
binding.textInput.text.toString()
)
memoDao.update(memo)
startActivity(toMainIntent)
}
binding.deleteButton.setOnClickListener {
memoDao.delete(memo)
startActivity(toMainIntent)
}
}
}
参考
【Android】Kotlin + Firestore + RecyclerViewを使ってTodoアプリを作る
【Android】Room を使ったサンプルと解説