Daniele Segato
10/01/2021, 11:24 PMSwipeable.animateTo
throw a JobCancellationException
when it's done instead of just terminating. (actually ModalBottomSheetState.show()
and ModalBottomSheetState.hide()
.
Is this a bug or was a design decision? And in the latter case what motivated it?
(more details in thread)Daniele Segato
10/01/2021, 11:28 PMsuspend fun show() {
val targetValue =
if (isHalfExpandedEnabled) HalfExpanded
else Expanded
animateTo(targetValue = targetValue)
}
uses Swipeable.animateTo
@ExperimentalMaterialApi
suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
latestNonEmptyAnchorsFlow.collect { anchors ->
try {
val targetOffset = anchors.getOffset(targetValue)
requireNotNull(targetOffset) {
"The target value must have an associated anchor."
}
animateInternalToOffset(targetOffset, anim)
} finally {
val endOffset = absoluteOffset.value
val endValue = anchors
// fighting rounding error once again, anchor should be as close as 0.5 pixels
.filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
.values.firstOrNull() ?: currentValue
currentValue = endValue
}
}
}
the animateInternalToOffset
is implemented like this:
private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
draggableState.drag {
var prevValue = absoluteOffset.value
animationTarget.value = target
isAnimationRunning = true
try {
Animatable(prevValue).animateTo(target, spec) {
dragBy(this.value - prevValue)
prevValue = this.value
}
} finally {
animationTarget.value = null
isAnimationRunning = false
}
}
}
And the drag
call there is actually a DefaultDraggableState.drag
for which this is the implementation
override suspend fun drag(
dragPriority: MutatePriority,
block: suspend DragScope.() -> Unit
): Unit = coroutineScope {
scrollMutex.mutateWith(dragScope, dragPriority, block)
}
now MutatorMutex.mutateWith
is implemented like such
suspend fun <T, R> mutateWith(
receiver: T,
priority: MutatePriority = MutatePriority.Default,
block: suspend T.() -> R
) = coroutineScope {
val mutator = Mutator(priority, coroutineContext[Job]!!)
tryMutateOrCancel(mutator)
mutex.withLock {
try {
receiver.block()
} finally {
currentMutator.compareAndSet(mutator, null)
}
}
}
and tryMutateOrCancel
does exactly what it sais
private fun tryMutateOrCancel(mutator: Mutator) {
while (true) {
val oldMutator = currentMutator.get()
if (oldMutator == null || mutator.canInterrupt(oldMutator)) {
if (currentMutator.compareAndSet(oldMutator, mutator)) {
oldMutator?.cancel()
break
}
} else throw CancellationException("Current mutation had a higher priority")
}
}
call oldMutator?.cancel()
in this case.Daniele Segato
10/01/2021, 11:29 PMIan Lake
10/01/2021, 11:52 PMIan Lake
10/01/2021, 11:53 PMDaniele Segato
10/02/2021, 12:01 AMjossiwolf
10/02/2021, 6:43 PMDaniele Segato
10/03/2021, 8:25 AMModalBottomSheet
reproduce it, should be easy to make onejossiwolf
10/03/2021, 2:08 PMDaniele Segato
10/03/2021, 2:31 PMIan Lake
10/03/2021, 3:26 PM