Mokelab Blog

#75 Jetpack ComposeでToDoアプリを作る - ToDoの削除機能

本シリーズの最後にToDo削除機能を追加してみます。

ポップアップメニューを追加する

削除はボタンひとつでできるとユーザー体験的によろしくないので、ポップアップメニューの中に追加します。View Systemの場合はapp:showAsActionneverで実現できましたが、Jetpack Composeではその部分も自作する必要があります。

@Composable
fun DetailTopBar(
  navController: NavController,
  todo: ToDo,
  toEdit: () -> Unit,
  deleteClicked: () -> Unit,
) {
  var showMenu by remember { mutableStateOf(false) }

  TopAppBar(
    navigationIcon = {
      IconButton(onClick = {
        navController.popBackStack()
      }) {
        Icon(Icons.Filled.ArrowBack, "Back")
      }
    },
    title = {
      if (todo._id == emptyToDoId) {
        Text(stringResource(id = R.string.loading))
      } else {
        Text(todo.title)
      }
    },
    actions = {
      if (todo._id != emptyToDoId) {
        IconButton(onClick = toEdit) {
          Icon(Icons.Filled.Edit, "Edit")
        }
        // 三点アイコン
        IconButton(onClick = { showMenu = !showMenu }) {
          Icon(Icons.Filled.MoreVert, "Menu")
        }
        // ポップアップメニュー部分
        DropdownMenu(
          expanded = showMenu,
          onDismissRequest = { showMenu = false }
        ) {
          DropdownMenuItem(onClick = {
            // 閉じた状態に戻す
            showMenu = false
            deleteClicked()
          }) {
            Text(stringResource(id = R.string.delete_todo))
          }
        }
      }
    }
  )
}

deleteClicked()の部分は状態を変化させるだけにしています。

val showDialog = remember { mutableStateOf(false) }

DetailTopBar(navController, todo.value, {
  navController.navigate("edit/${todo.value._id}")
}, { // deleteClickedの部分
  showDialog.value = true
})

AlertDialogを表示する

View Systemの場合、DialogFragmentを作ったりとやや面倒でしたが、Jetpack Composeでは表示したいタイミングでAlertDialog()を呼ぶだけです。

@Composable
fun DetailBody(
  todo: ToDo,
  showDialog: MutableState<Boolean>,
  performDelete: () -> Unit
) {
  ...
  if (showDialog.value) {
    AlertDialog(onDismissRequest = {
      showDialog.value = false
    }, title = {
      Text(stringResource(id = R.string.delete_message))
    }, confirmButton = {
      TextButton(onClick = {
        showDialog.value = false
        performDelete()
      }) {
        Text(stringResource(id = android.R.string.ok))
      }
    }, dismissButton = {
      TextButton(onClick = { showDialog.value = false }) {
        Text(stringResource(id = android.R.string.cancel))
      }
    }
    )
  }
}

performDeleteの部分はViewModelのメソッドを呼ぶだけです。

DetailBody(todo.value, showDialog) { // performDeleteの部分
  viewModel.delete(todo.value)
}

削除処理を実装する

ここからはView System版の時と同じです。

@HiltViewModel
class ToDoDetailViewModel @Inject constructor(
  private val repo: ToDoRepository
) : ViewModel() {
  ...
  val errorMessage = MutableStateFlow("")
  val deleted = MutableStateFlow(false)

  fun delete(todo: ToDo) {
    viewModelScope.launch {
      try {
        repo.delete(todo)
        deleted.value = true
      } catch (e: Exception) {
        errorMessage.value = e.message ?: ""
      }
    }
  }
}

動作確認をする

ポップアップメニューから削除を選び、ダイアログでOKをタップすると削除されました。

ここまで作業したものはこちらにあります。

本サイトではサービス向上のため、Google Analyticsを導入しています。分析にはCookieを利用しています。