[DON-2483] Fix busy waiting and improve thread safety in BpkModal and…#2596
Conversation
Generated by 🚫 Danger Kotlin against 5abb65b |
Generated by 🚫 Danger Kotlin against 54a79b0 |
There was a problem hiding this comment.
Pull request overview
Enhances Backpack Compose modal dismissal by removing busy-waiting, introducing a dismiss-animation completion callback, and simplifying modal coroutine usage/state transitions.
Changes:
- Added
onDismissAnimationCompletiontoBpkModalandBpkAppSearchModal. - Refactored
BpkModalStateto use callback-based hide completion tracking instead of suspend/busy-waiting. - Updated demo/story usages to call
state.hide()directly (no per-call coroutine scopes).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/modal/BpkModalState.kt | Removes busy-waiting and adds callback-based hide completion tracking with a stored scope. |
| backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/modal/BpkModal.kt | Adds onDismissAnimationCompletion and attempts to observe visibility/idle state to trigger it. |
| backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/appsearchmodal/BpkAppSearchModal.kt | Exposes the new completion callback and simplifies close handling to state.hide(). |
| app/src/main/java/net/skyscanner/backpack/demo/compose/ModalStory.kt | Updates demo to call non-suspending hide() directly. |
| app/src/main/java/net/skyscanner/backpack/demo/compose/ImageGalleryStory.kt | Updates demo to call non-suspending hide() directly. |
| fun hide(onHidden: (() -> Unit)? = null) { | ||
| animateState(false) | ||
| onHidden?.let { callback -> | ||
| _pendingHideAnimationCallback = callback | ||
| _scope?.launch { | ||
| snapshotFlow { isVisible.isIdle && !isVisible.currentState }.distinctUntilChanged().filter { it }.collect { | ||
| callback.invoke() | ||
| _pendingHideAnimationCallback = null | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The snapshotFlow(...).collect { ... } never completes, so the launched coroutine will remain active and may invoke callback again on subsequent hide completions (even after _pendingHideAnimationCallback is cleared). Use a one-shot terminal operator (e.g., first { it } / take(1)) and consider storing/cancelling a previous Job when hide() is called again to avoid multiple concurrent collectors.
| ) { | ||
| suspend fun show() { | ||
|
|
||
| private var _pendingHideAnimationCallback: (() -> Unit)? = null |
There was a problem hiding this comment.
_pendingHideAnimationCallback is assigned and cleared but never read/used to control behavior in the current diff, which makes the state harder to reason about. Either remove it or use it explicitly (e.g., to prevent multiple callbacks/collectors, or to no-op if a newer callback has replaced an older one).
| val scope = rememberCoroutineScope() | ||
| LaunchedEffect(Unit) { | ||
| modalState.setCoroutineScope(scope) | ||
| } |
There was a problem hiding this comment.
Setting the scope via LaunchedEffect(Unit) introduces a timing gap where hide(onHidden=...) can run before _scope is set, causing the completion callback to never be observed (and _pendingHideAnimationCallback to remain set). Prefer setting the scope synchronously (e.g., via SideEffect { ... } or direct assignment if acceptable) and/or handle the “scope not set yet” case by deferring the subscription until the scope becomes available.
backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/modal/BpkModal.kt
Outdated
Show resolved
Hide resolved
...-compose/src/main/kotlin/net/skyscanner/backpack/compose/appsearchmodal/BpkAppSearchModal.kt
Outdated
Show resolved
Hide resolved
|
Please add a stress/regression test for Scope
Validation
Why this is useful
|
Generated by 🚫 Danger Kotlin against 3cb3678 |
… BpkAppSearchModal
…nDismissAnimationCompletion and added missing tests for BpkModals hide state
3cb3678 to
0b77344
Compare
Generated by 🚫 Danger Kotlin against 0b77344 |
DON-2483 Fix busy waiting and improve thread safety in BpkModal and BpkAppSearchModal
This pull request enhances the modal dismissal flow in the Backpack Compose modal components, making it easier to respond to the end of the dismiss animation and simplifying coroutine usage. The main improvements are the addition of a callback for dismiss animation completion, refactoring of coroutine management, and simplification of modal state transitions.
See the DEMO PR of the applied changes to the main app here: https://github.com/Skyscanner/skyscanner-app/pull/44690
(NOTE: You will need to publish backpack locally and use mavenLocal in the main app branch)
Modal dismissal enhancements:
onDismissAnimationCompletioncallback parameter to bothBpkModalandBpkAppSearchModal, allowing consumers to react when the dismiss animation finishes. [1] [2]BpkModalto invokeonDismissAnimationCompletionvia aLaunchedEffectthat observes the modal's visibility state.State management and coroutine handling:
BpkModalStateto remove suspend functions and internal coroutine management in favor of a simpler, callback-based approach usingsnapshotFlowand a stored coroutine scope.rememberBpkModalStateto set up and inject a coroutine scope into the modal state for use in animation completion tracking.Code cleanup:
rememberCoroutineScopefromBpkAppSearchModal.onClickhandler for the close button inBpkAppSearchModalto directly callstate.hide().Remember to include the following changes:
README.mdIf you are curious about how we review, please read through the code review guidelines