Run this in Skip Lite. (I think it would repro in Skip Fuse, too, but #687 gets in the way.)
Tap "Push", and quickly tap the back button before the task completes.
struct ContentView: View {
@State var id: Int = 0
@State var result: String? = nil
var body: some View {
NavigationStack {
NavigationLink("Push") {
VStack {
Text("Hello, World!")
if let result {
Text(result)
}
Button("Increment ID") {
id += 1
}
}
.task(id: id) {
let id = self.id
result = await Task {
try? await Task.sleep(for: .seconds(2))
return "Hello, World! \(id)"
}.value
logger.debug("result: isCancelled: \(Task.isCancelled) result: \(result ?? "nil")")
}
}
}
}
}
Expected: On iOS, the log fires, saying: "result: isCancelled: true result: Hello, World! 0"
Actual: The app crashes.
FATAL EXCEPTION: main
Process: org.appfair.app.ShowcaseLite, PID: 18218
skip.lib.CancellationError: androidx.compose.runtime.LeftCompositionCancellationException: The coroutine scope left the composition
at skip.lib.Task.value(Concurrency.kt:33)
at skip.lib.Task$value$1.invokeSuspend(Unknown Source:14)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:233)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:175)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:147)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:214)
at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:221)
at kotlinx.coroutines.ChildContinuation.invoke(CancellableContinuationImpl.kt:698)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1604)
at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:826)
at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:786)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:702)
at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:668)
at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1580)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1604)
at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:826)
at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:786)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:702)
at kotlinx.coroutines.JobSupport.cancelInternal(JobSupport.kt:663)
at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:648)
at androidx.compose.runtime.LaunchedEffectImpl.onForgotten(Effects.kt:283)
at androidx.compose.runtime.internal.RememberEventDispatcher.dispatchRememberObservers(RememberEventDispatcher.kt:211)
at androidx.compose.runtime.CompositionImpl.disposeUnusedMovableContent(Composition.kt:1099)
at androidx.compose.runtime.Recomposer.discardUnusedMovableContentState(Recomposer.kt:1519)
at androidx.compose.runtime.Recomposer.access$discardUnusedMovableContentState(Recomposer.kt:159)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:771)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:79)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
at android.os.Handler.handleCallback(Handler.java:1070)
at android.os.Handler.dispatchMessage(Handler.java:125)
at android.os.Looper.dispatchMessage(Looper.java:333)
at android.os.Looper.loopOnce(Looper.java:263)
at android.os.Looper.loop(Looper.java:367)
at android.app.ActivityThread.main(ActivityThread.java:9287)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@a954de9, androidx.compose.runtime.BroadcastFrameClock@a855a6e, androidx.compose.runtime.LaunchedEffectImpl@87d8045, StandaloneCoroutine{Cancelling}@b69350f, AndroidUiDispatcher@290599c]
Caused by: androidx.compose.runtime.LeftCompositionCancellationException: The coroutine scope left the composition
Run this in Skip Lite. (I think it would repro in Skip Fuse, too, but #687 gets in the way.)
Tap "Push", and quickly tap the back button before the task completes.
Expected: On iOS, the log fires, saying: "result: isCancelled: true result: Hello, World! 0"
Actual: The app crashes.