Dialogのざっくり紹介
Jetpack ComposeのDialogはAlertDialogを使うことで、簡単にダイアログの定義をすることができるため、XMLでの作成と比べてとても楽です。
@Composable
fun SampleDialog() {
AlertDialog(
onDismissRequest = {
// Dialogの枠外タップ時の挙動
},
confirmButton = {
// OKとか確認とかのボタンの実装
// 空だとボタン表示自体がされなくなる
},
dismissButton = {
// キャンセル系のボタンの実装
// 空でもOK
},
title = {
// Dialogのタイトル
},
text = {
// Dialogの本文
// といいつつここにButtonやらを仕込んでボタン並んでるようなDialogも作れる
// textというよりもbody的な意味合いの方が強い
}
)
}
こんな簡単な実装で使いまわせるDialogを実装することができます。
ちょっと面倒なところ
Dialog自体の表示処理やdismiss時の非表示処理は自分で記載する必要があります。
ここはFragment, XMLでのDialogとは異なる部分です。
簡単にやるならDialogのクラス内でremember
を使用したStateによる表示管理がよく使われています。
@Composable
fun SampleDialog() {
// Stateによる状態監視を追加
var show by remember { mutableStateOf(true) }
if (!show) {
return
}
AlertDialog(
onDismissRequest = {
show = false
},
// 省略
)
}
あるいは
@Composable
fun SampleScreen() {
Scaffold(
// 省略
) {
var show by remember { mutableStateOf(true) }
if (show) {
SampleDialog(
// callbackでボタンタップを受け取ってState切り替え
onDismiss = {
show = false
}
)
}
}
}
などですね。
Compose上のStateによる表示管理の問題
Compose側でDialogの表示制御をすることになるので、たとえば通信などの非同期処理な何かしらの処理をViewModel以下で実施後にその結果によってDialogを表示させたい時に対応できません。
解決策
UiEventを用いることで、自好きなタイミングでDialogの表示切り替えをすることができるようになります。
実装例
@Composable
fun SampleScreen(
state: SampleScreenState
) {
Scaffold(
// 省略
) {
// uiEventの状態監視
val uiEvents by state.uiEvents.collectAsState()
uiEvents.forEach { event ->
when (event) {
SampleScreenViewModel.UiEvent.ShowDialog -> {
SampleDialog(
onDismiss = {
state.consume(target = event)
}
)
}
}
}
}
}
@Composable
private fun rememberSampleScreenState(
viewModel: SampleScreenViewModel = viewModel {
SampleScreenViewModel()
}
): SampleScreenState = remember {
SampleScreenState(
viewModel = viewModel
)
}
@State
class SampleScreenState(
private val viewModel: SampleScreenViewModel
) {
val uiEvents = viewModel.uiEvents
fun showDialog() = viewModel.showDialog()
fun consume(target: SampleScreenViewModel.UiEvent) =
viewModel.consume(target = target)
}
class SampleScreenViewModel() : ViewModel() {
sealed interface UiEvent {
object ShowDialog : UiEvent
// 引数をつけたい場合は
// data class ShowDialog(val arg: String) : UiEvent
}
private val _uiEvents: MutableStateFlow<List<UiEvent>> =
MutableStateFlow(emptyList())
val uiEvents = _uiEvents.asStateFlow()
// UiEventにShowDialogを追加する
fun showDialog() {
_uiEvents.update {
it + UiEvent.ShowDialog
}
}
// UiEventからtargetで指定したUiEventを削除
fun consume(target: UiEvent) {
_uiEvents.update { e -> e.filterNot { it == target } }
}
}
これによって、画面上のボタンタップ時にDialogを表示したり、非同期処理の完了後にDialogを表示したりなど、かなり広い範囲の表示パターンでの表示管理をカバーすることができます。
Snackbarなどのその他UIの表示管理にも有効な方法ですので、お困りの際はぜひご活用ください。