#62 AndroidでToDoアプリを作る - ToDoの削除機能
今回はToDoの削除機能を実装してみます。
削除の手順
削除は気軽にできないほうが安全なので、次の手順で削除が実行されるようにしてみます。
- 詳細画面で右上メニューの3点アイコンをタップ
- 「削除」をタップ
- 確認ダイアログを表示し、OKをタップ
- 削除を実行し、リスト画面に戻る
順に実装していきましょう。
メニューに削除の項目を追加する
まず、メニューに項目を追加しましょう。今回はアイコン表示しないので、vector assetsの追加は行いません。
詳細画面のメニューに追加するので、menu_detail.xml
を開きます。そして次のようにMenu
Itemを追加します。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_baseline_edit_24"
android:title="Edit"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_delete"
android:title="@string/delete"
app:showAsAction="never" />
</menu>
IDはaction_delete
としておきます。アイコン(アクション)として表示させたくないので、app:showAsAction
にはnever
を指定します。
確認用のダイアログを追加する
次に削除確認用のダイアログを追加します。ダイアログはDialogFragment
を使って作ります。今回はcom.mokelab.mytodo.dialog
パッケージを作り、その中にConfirmDialogFragment
というクラスを追加してみます。
package com.mokelab.mytodo.dialog
import androidx.fragment.app.DialogFragment
class ConfirmDialogFragment: DialogFragment() {
}
DialogFragment
では、onCreateDialog()
をオーバーライドし、その中でAlertDialogを作ります。importを追加する際、AndroidX版を追加しましょう。
class ConfirmDialogFragment: DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireActivity()).apply {
setMessage("このToDoを削除しますか?")
setPositiveButton(android.R.string.ok, listener)
setNegativeButton(android.R.string.cancel, listener)
}.create()
}
private val listener = DialogInterface.OnClickListener { _, which ->
// 実装は次節
}
}
setPositiveButton()
でOKボタンを追加し、setNegativeButton()
でキャンセルボタンを追加します。ボタンタップ時の処理はlistener
の中で記述します。前回の「呼び出し元フラグメントに結果を伝える」と同様に結果を呼び出し元に伝えます。
class ConfirmDialogFragment: DialogFragment() {
...
private val listener = DialogInterface.OnClickListener { _, which ->
val data = bundleOf("result" to which)
parentFragmentManager.setFragmentResult("confirm", data)
}
}
ナビゲーショングラフに追加する
ダイアログもナビゲーショングラフの遷移先にすることができます。ConfirmDialogFragment
をnav_main.xmlに追加します。

追加したら、詳細画面からの遷移を追加します。

メニュータップ時の処理を記述する
「削除」をタップした際、ダイアログが表示されるようにします。ToDoDetailFragment
のonOptionsItemSelected()
を修正します。
@AndroidEntryPoint
class ToDoDetailFragment: Fragment(R.layout.todo_detail_fragment) {
...
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_edit -> {
...
}
// このブロックを追加
R.id.action_delete -> {
findNavController()
.navigate(R.id.action_toDoDetailFragment_to_confirmDialogFragment)
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
ダイアログの選択結果を扱う
確認ダイアログでボタンがタップされた後の処理をToDoDetailFragment
に記述します。扱い方は前回と同様にsetFragmentResultListener()
を使います。OKがタップされていたらViewModelに削除処理を依頼します(実装は次節)。
class ToDoDetailFragment: Fragment(R.layout.todo_detail_fragment) {
private val vm: ToDoDetailViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
setFragmentResultListener("confirm") { _, data ->
val which = data.getInt("result", DialogInterface.BUTTON_NEGATIVE)
if (which != DialogInterface.BUTTON_POSITIVE) return@setFragmentResultListener
vm.delete() // 次節で実装する
}
}
}
ToDo削除処理の実装
実際のToDo削除処理はこれまでと同様にViewModelで実装します。ToDoDetailViewModel
を次のようにします。
@HiltViewModel
class ToDoDetailViewModel @Inject constructor(
private val toDoRepository: ToDoRepository,
savedStateHandle: SavedStateHandle
): ViewModel() {
val todo = savedStateHandle.getLiveData<ToDo>("todo")
val errorMessage = MutableLiveData<String>()
val deleted = MutableLiveData<Boolean>()
fun delete() {
viewModelScope.launch {
try {
val todo = this@ToDoDetailViewModel.todo.value ?: return@launch
toDoRepository.delete(todo)
deleted.value = true
} catch (e: Exception) {
errorMessage.value = e.message
}
}
}
}
Hiltの導入がまだだったので、クラスに対し@HiltViewModel
をつけ、コンストラクタに@Inject constructor
をつけます。また、削除処理で使うのでToDoRepositoryをプロパティとしてもたせます。
削除メソッドの中ではこれまでと同様にリポジトリのメソッドを呼ぶだけの実装にします。削除対象のToDoはtodo
プロパティの中に入っているので、それを使います。
ToDoRepository
にはまだdelete()
メソッドがないので、Alt+Enterで追加していきます。インターフェースは次のようにsuspend fun
にしておきます。
interface ToDoRepository {
...
suspend fun delete(value: ToDo)
}
実装クラスとなるToDoRepositoryImpl
では、DAOのメソッドを呼ぶだけにします。
class ToDoRepositoryImpl @Inject constructor(
private val dao: ToDoDAO
): ToDoRepository {
...
override suspend fun delete(todo: ToDo) {
dao.delete(todo)
}
}
削除処理完了後の処理の記述
リポジトリにToDoを削除させた後の処理を記述します。ToDoDetailFragment
でLiveData
を監視します。
@AndroidEntryPoint
class ToDoDetailFragment: Fragment(R.layout.todo_detail_fragment) {
private val vm: ToDoDetailViewModel by viewModels()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
this._binding = TodoDetailFragmentBinding.bind(view)
...
vm.errorMessage.observe(viewLifecycleOwner) { msg ->
if (msg.isEmpty()) return@observe
Snackbar.make(requireView(), msg, Snackbar.LENGTH_SHORT).show()
vm.errorMessage.value = ""
}
vm.deleted.observe(viewLifecycleOwner) {
// メイン画面まで戻る。戻り先を指定しないとタイミングによっては
// ダイアログを閉じる操作になってしまう
findNavController().popBackStack(R.id.mainFragment, false)
}
}
...
}
削除の完了を表すdeleted
では、引数ありpopBackStack()
を使います。引数なしの場合、処理のタイミングによってはダイアログを閉じる操作となり、メイン画面に戻らないことがあるためです。
動作確認してみる
ここまでできたら動作確認をしてみましょう。
当然のような動きですが、削除を選び、確認ダイアログでOKを押すと削除されました。
まとめ
今回はToDoの削除機能を実装してみました。ダイアログの作り方や扱い方はこのシリーズで初めて登場したので、今後のアプリ開発でも使えるようになっておきましょう。