#74 Jetpack ComposeでToDoアプリを作る - ToDoの編集機能
ToDoの詳細表示を実装したので、次は編集機能を実装してみます。
TopAppBarにアクションを追加する
編集画面への遷移は、詳細画面の右上に鉛筆アイコンを配置し、そこをタップした際に遷移するようにしてみます。
@Composable
fun DetailTopBar(
navController: NavController,
todo: ToDo,
toEdit: () -> Unit,
) {
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")
}
}
}
)
}
TopAppBarにアクションアイコンを追加するには、actions
パラメータでIconButton
を使います。
ToDo編集画面のレイアウトを作る
レイアウト自体は作成画面と同じものでよいでしょう。
@Composable
fun EditToDoBody(
title: MutableState<String>,
detail: MutableState<String>,
) {
Column {
TextField(
value = title.value,
onValueChange = { title.value = it },
label = { Text(stringResource(id = R.string.todo_title)) },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
TextField(
value = detail.value,
onValueChange = { detail.value = it },
label = { Text(stringResource(id = R.string.todo_detail)) },
modifier = Modifier
.weight(1.0f, true)
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
ルート部分は、編集対象のToDoの読み込みが完了しているかどうかで分岐させます。
@Composable
fun EditToDoScreen(
navController: NavController,
viewModel: EditToDoViewModel,
) {
val scaffoldState = rememberScaffoldState()
val todo = viewModel.todo.collectAsState(emptyToDo)
// 読み込み中を表示
if (todo.value._id == emptyToDoId) {
Scaffold(
scaffoldState = scaffoldState,
topBar = {
EditTopBar(navController, null)
},
) {
CircularProgressIndicator()
}
return
}
val title = rememberSaveable { mutableStateOf(todo.value.title) }
val detail = rememberSaveable { mutableStateOf(todo.value.detail) }
Scaffold(
scaffoldState = scaffoldState,
topBar = {
EditTopBar(navController) {
viewModel.save(todo.value, title.value, detail.value)
}
},
) {
EditToDoBody(title, detail)
}
}
Topbar部分も作成画面と同じようにしました。読み込み中に編集完了ボタンを押されると困るので、save
がnull
かどうかで分岐させています。
@Composable
fun EditTopBar(navController: NavController, save: (() -> Unit)?) {
TopAppBar(
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(Icons.Filled.ArrowBack, "Back")
}
},
title = {
Text(stringResource(id = R.string.edit_todo))
},
actions = {
if (save != null) {
IconButton(onClick = save) {
Icon(Icons.Filled.Done, "Save")
}
}
}
)
}
更新処理を実装する
EditToDoViewModel
のsave()
を実装していきます。といっても作成とほぼ同じような処理ですが。。。
@HiltViewModel
class EditToDoViewModel @Inject constructor(
private val repo: ToDoRepository
) : ViewModel() {
private val todoId = MutableStateFlow(-1)
@ExperimentalCoroutinesApi
val todo: Flow<ToDo> = todoId.flatMapLatest { todoId -> repo.getById(todoId) }
val errorMessage = MutableStateFlow("")
val done = MutableStateFlow(false)
fun setId(todoId: Int) {
this.todoId.value = todoId
}
fun save(todo: ToDo, title: String, detail: String) {
if (title.trim().isEmpty()) {
errorMessage.value = "Input title"
return
}
viewModelScope.launch {
try {
repo.update(todo, title, detail)
done.value = true
} catch (e: Exception) {
errorMessage.value = e.message.toString()
}
}
}
}
更新処理後の処理を実装する
こちらも作成の時と同様です。
@Composable
fun EditToDoScreen(
navController: NavController,
viewModel: EditToDoViewModel,
) {
val scaffoldState = rememberScaffoldState()
val todo = viewModel.todo.collectAsState(emptyToDo)
if (todo.value._id == emptyToDoId) {
Scaffold(
scaffoldState = scaffoldState,
topBar = {
EditTopBar(navController, null)
},
) {
CircularProgressIndicator()
}
return
}
val title = rememberSaveable { mutableStateOf(todo.value.title) }
val detail = rememberSaveable { mutableStateOf(todo.value.detail) }
val errorMessage = viewModel.errorMessage.collectAsState()
val done = viewModel.done.collectAsState()
if (errorMessage.value.isNotEmpty()) {
LaunchedEffect(scaffoldState.snackbarHostState) {
scaffoldState.snackbarHostState.showSnackbar(
message = errorMessage.value
)
viewModel.errorMessage.value = ""
}
}
if (done.value) {
// 再コンポーズ時にもう一度実行されたら困る
viewModel.done.value = false
navController.popBackStack()
}
Scaffold(
scaffoldState = scaffoldState,
topBar = {
EditTopBar(navController) {
viewModel.save(todo.value, title.value, detail.value)
}
},
) {
EditToDoBody(title, detail)
}
}
動作確認してみる
編集画面に遷移し、編集が完了すると詳細画面に戻ってきました。Roomの機能で最新のデータ取得が行われ、表示も更新されています。
ここまで作業したものはこちらにあります。