Skip to content

Instantly share code, notes, and snippets.

@ckarthickit
Last active January 25, 2022 13:57
Show Gist options
  • Select an option

  • Save ckarthickit/3c29388de382e514bcd11836e089dfcc to your computer and use it in GitHub Desktop.

Select an option

Save ckarthickit/3c29388de382e514bcd11836e089dfcc to your computer and use it in GitHub Desktop.
Compose Utilities
Utility functions
class ComposeCountRef(var counter: Int)
@Suppress("NOTHING_TO_INLINE")
@Composable
inline fun LogCompositions(tag: String, msg: String) {
val compositionCountRef = remember { ComposeCountRef(0) }
SideEffect {
compositionCountRef.counter ++
}
Timber.tag(tag).d("Compositions $msg : ${compositionCountRef.counter}")
if(compositionCountRef.counter > 0) {
Exception("Compositions recomposing_$msg").printStackTrace()
}else {
Exception("Compositions composing_$msg").printStackTrace()
}
}
import android.os.SystemClock
import androidx.compose.foundation.Indication
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import timber.log.Timber
fun Modifier.debounceSelectable(
debounceInMillis: Long = 2000,
selected: Boolean,
interactionSource: MutableInteractionSource,
indication: Indication?,
enabled: Boolean = true,
role: Role? = null,
onClick: () -> Unit
) = debounceSelectableInternal(
debounceInMillis = debounceInMillis,
selected = selected,
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
role = role,
onClick = onClick
)
fun Modifier.debouncedClickable(
debounceInMillis: Long = 2000,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
indication: Indication?,
onClick: () -> Unit
) = debounceSelectableInternal(
debounceInMillis = debounceInMillis,
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
indication = indication,
onClick = onClick
)
internal fun Modifier.debounceSelectableInternal(
debounceInMillis: Long = 2000,
selected: Boolean? = null,
interactionSource: MutableInteractionSource? = null,
indication: Indication?,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "debounceSelectable"
properties["debounceInMillis"] = debounceInMillis
properties["selected"] = selected
properties["interactionSource"] = interactionSource
properties["indication"] = indication
properties["enabled"] = enabled
properties["onClickLabel"] = onClickLabel
properties["role"] = role
properties["onClick"] = onClick
}) {
val currentOnClick = rememberUpdatedState(newValue = onClick)
var lastClickTime by remember {
mutableStateOf(SystemClock.elapsedRealtime())
}
Modifier
.clickable(
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
indication = indication ?: LocalIndication.current,
interactionSource = interactionSource ?: remember { MutableInteractionSource() },
onClick = {
val timeSinceLastClick = SystemClock.elapsedRealtime() - lastClickTime
if (timeSinceLastClick > debounceInMillis) {
lastClickTime = SystemClock.elapsedRealtime()
currentOnClick.value.invoke()
} else {
Timber.d("Ignoring Click until ${debounceInMillis - timeSinceLastClick}ms")
}
}
).apply {
if (selected != null) {
this.semantics {
this.selected = selected
}
}
}
}
/**
* A Side Effect that is invoked whenever the [Lifecycle.Event] fired by the [Lifecycle]
* associated with the current [LocalLifecycleOwner]
* matches the passed [lifecycleEvent] param
*/
@Composable
fun LifecycleLaunchedEffect(
lifecycleEvent: Lifecycle.Event = Lifecycle.Event.ON_RESUME,
onLifecycleEvent: suspend CoroutineScope.() -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val currentLifecycleEventCallback = rememberUpdatedState(newValue = onLifecycleEvent)
val lifecycleEventObserver = remember {
LifecycleEventObserver { _, event ->
if (event == lifecycleEvent) {
coroutineScope.launch(block = currentLifecycleEventCallback.value)
}
}
}
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycle, lifecycleEventObserver) {
lifecycle.addObserver(lifecycleEventObserver)
onDispose {
lifecycle.removeObserver(lifecycleEventObserver)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment