LoginSignup
6
8

More than 1 year has passed since last update.

【Android】Kotlin + Room + RecyclerViewを使ってメモアプリを作る

Last updated at Posted at 2022-02-27

はじめに

RoomとRecyclerViewを使った簡単なメモアプリを作成したので共有します。
RoomはSQLiteを活用したローカルデータベースであり、公式でも推奨されています。
参考:Room を使用してローカル データベースにデータを保存する

今回はメモの追加と編集、削除ができる簡単なアプリを作成しました。またAdapterにはListAdapterを採用しています。
ソースコードはこちらから
gif.gif

環境

  • Kotlin 1.6.10
  • Android Studio Bumblebee | 2021.1.1
  • Room 2.2.2
  • targetSDK: 32

概要

  1. Roomの初期設定
  2. View Bindingの設定
  3. Entityの作成
  4. Daoの作成
  5. RoomDatabaseの作成
  6. ListAdapterの作成
  7. Mainactivityで一覧表示
  8. EditActivityでメモの追加と編集、削除

1. Roomの初期設定

app/build.gradle の dependencies に以下を追加

app/build.gradle
    def room_version = "2.2.2"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

app/build.gradle の plugins に以下を追加

app/build.gradle
    id "org.jetbrains.kotlin.kapt"

Bumblubeeからライブラリの導入の仕方が変わったので注意しましょう。

2. View Bindingの設定

build.gradleのandroidに以下のコードを追加します

build.gradle
    buildFeatures{
        viewBinding true
    }

MainActivity.ktに以下のコードを追加します

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を定義します

Memo.kt
@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を定義します

MemoDao.kt
@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を定義します

MemoDatabase.kt
@Database(entities = [Memo::class], version = 1)
abstract class MemoDatabase : RoomDatabase() {
    abstract fun memoDao(): MemoDao
}

6. ListAdapterの作成

memo_itemの作成

memo_item.xml
<?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の作成

MemoAdapter.kt
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を変更していきます

activity_main.xml
<?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>
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) }
        //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でメモの追加と編集、削除

activity_edit
<?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>
EditActivity.kt
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 を使ったサンプルと解説

6
8
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
6
8