What is a better approach to solve this? When the...
# compose
s
What is a better approach to solve this? When the user interacts with zoom controls I want to show a toast that displays the current zoom level. It should disappear a second after the user stopped interacting. The lambda onUserInput() is called when the interaction happens and switches the showZoomToastState to "true". The problem is of course that when the user interacts further the toast starts to flicker. I guess I need to cancel all previous coroutines, so that they won't reach the last line of code. Is there something to solve this in a nice way?
Copy code
val showZoomToastState = remember { mutableStateOf(false) }

val coroutineScope = rememberCoroutineScope()

val onZoomUserInput: () -> Unit = {

    coroutineScope.launch {

        showZoomToastState.value = true

        delay(ZOOM_TOAST_DURATION_MS)

        showZoomToastState.value = false
    }
}
1
s
I’d do something like this instead
Copy code
value class ZoomLevel(value: Float)

val zoomLevelChannel = remember { 
  MutableSharedFlow<ZoomLevel>(
    extraBufferCapacity = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
  ) 
}
val zoomLevelToShow: Float? by remember { mutableFloatStateOf(null) }
... onZoomUserInput = { zoomLevel: Float ->
  zoomLevelChannel.tryEmit(ZoomLevel(zoomLevel))
}
...
LaunchedEffect(zoomLevelChannel) {
  zoomLevelChannel.collectLatest { zoomLevel ->
    zoomLevelToShow = zoomLevel.value
    delay(1.seconds)
    zoomLevelToShow = null
  }
}
if (zoomLevelToShow != null) {
  ZoomLevelToast(zoomLevelToShow)
}
Keep the channel as your safe place to put all the events to, and you can safely do a collectLatest on it in a LaunchedEffect, dropping all the previous values since you do not really care about them if there is a new zoom level, and you can control dismissing the dialog after 1 second that there was no event by setting the state back to null afterwards. -Edited after Albert’s suggestion to use MutableSharedFlow instead
a
I would use a
MutableSharedFlow
, which should be more efficient. Here’s something similar.
s
Ah cool! More efficient because it will internally drop the new emissions anyway instead of relying on the
collectLatest
to drop the old (now obsolete) events right?
a
More because shared flows are themselves more efficient than channels because shared flows don’t need synchronization. See https://elizarov.medium.com/shared-flows-broadcast-channels-899b675e805c.
thank you color 2
s
Thank you both! 🙂 Works great.
Copy code
val showZoomToastState = remember { mutableStateOf(false) }

val showZoomToastStateFlow = remember {
    MutableSharedFlow<Unit>(
        extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
}

val onZoomUserInput: () -> Unit = {
    showZoomToastStateFlow.tryEmit(Unit)
}

LaunchedEffect(showZoomToastStateFlow) {

    showZoomToastStateFlow.collectLatest {

        showZoomToastState.value = true

        delay(ZOOM_TOAST_DURATION_MS)

        showZoomToastState.value = false
    }
}
s
Awesome! Probably wanna rename the
showZoomToastStateFlow
into
showZoomToastSharedFlow
, in the variable name, so that you do not confuse yourself or another colleague in the future 😄
😅 1
👍 1