Mark
06/10/2020, 7:16 AMEvent
wrapper since dialog is persisted through configuration changes when using navigation component. The view layer would be responsible for `complete`ing the deferrable when the user clicks an item.
val itemSelectorLiveData = MutableLiveData<Event<Pair<List<String>, CompletableDeferred<String>>>>()
suspend fun doSomethingFunButWorthwhile(): Boolean {
val items: List<String> = workOutWhichItemsToChooseFrom()
val deferredItem = CompletableDeferred<String>()
itemSelectorLiveData.postValue(Event(Pair(items, deferredItem)))
// wait for user to select item
val selectedItem = deferredItem.await()
return doSomethingWorthwhile(selectedItem)
}
CompletableDeferred
(obtained via a shared viewmodel) in order to complete it. Alternatively I could use the new (alpha) fragment result API but then still the calling fragment needs to grab a reference to that CompletableDeferred
so doesn’t offer much in the way of advantage.Adam Powell
06/10/2020, 2:17 PMdoSomethingFun
proceed, and since it looks to be a one-time operation you can probably use suspendCancellableCoroutine
and store the continuation instead of a CompletableDeferred
await
Mutex.withLock {}
block to keep more than one call to doSomethingFun
from stepping on each other's toes - it'll form an orderly queue for concurrent callersclass DialogPrompt(
val options: List<String>,
private val continuation: Continuation<String>
) {
fun select(item: String) {
require(item in options) { "$item is not a valid selection from $options" }
continuation.resume(item)
}
}
class FooModel {
private val promptMutex = Mutex()
// Must hold promptMutex to modify
private val _dialogPrompt = MutableLiveData<DialogPrompt?>()
val dialogPrompt: LiveData<DialogPrompt?> get() = _dialogPrompt
suspend fun doSomethingFunButWorthwhile(): Boolean {
val items = workOutWhichItemsToChooseFrom()
val dialogResult = prompt(items)
return doSomethingWorthwhile(dialogResult)
}
private suspend fun prompt(options: List<String>): String = promptMutex.withLock {
withContext(Dispatchers.Main) {
try {
suspendCancellableCoroutine { continuation ->
_dialogPrompt.value = DialogPrompt(options, continuation)
}
} finally {
_dialogPrompt.value = null
}
}
}
}
DialogPrompt
that resumes the continuation with a CancellationException
if the user cancels the promptMark
06/12/2020, 1:48 PMselect
could be called multiple times (e.g. after first call and before the livedata has a chance to be set to null)?Event
wrapper, I guess without it, I’d have to check whether the dialog has already been added to the fragment managerAdam Powell
06/12/2020, 2:40 PMCancellationException
as a teardown, it's much better to lean into it.Mark
06/13/2020, 2:09 AMnavigate
to the dialog fragment. At this point, is it recommended to not use any arguments (navArgs
) but rather let the dialog fragment grab the livedata directly from, say, a navGraphViewModels
? In the case where, the String
arg is actually something, say, non-parcelable, this seems like the only reasonable way, but even for String
it seems in some way cleaner because then the fragment is working entirely on DialogPrompt
which ties the input and output together.CancellationException
, since the continuation is a CancellableContinuation
better to just call cancel()
in dialog cancel listeners I guess.