Skip to content

Instantly share code, notes, and snippets.

@TylerMcCraw
Forked from krizzu/SnackbarController.kt
Created March 11, 2025 20:15

Revisions

  1. @krizzu krizzu created this gist Jul 28, 2024.
    113 changes: 113 additions & 0 deletions SnackbarController.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,113 @@
    import androidx.compose.material3.SnackbarDuration
    import androidx.compose.material3.SnackbarHostState
    import androidx.compose.material3.SnackbarResult
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.CompositionLocalProvider
    import androidx.compose.runtime.DisposableEffect
    import androidx.compose.runtime.Immutable
    import androidx.compose.runtime.ReadOnlyComposable
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.rememberCoroutineScope
    import androidx.compose.runtime.staticCompositionLocalOf
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.channels.Channel
    import kotlinx.coroutines.launch
    import kotlin.coroutines.EmptyCoroutineContext

    private val LocalSnackbarController = staticCompositionLocalOf {
    SnackbarController(
    host = SnackbarHostState(),
    scope = CoroutineScope(EmptyCoroutineContext)
    )
    }
    private val channel = Channel<SnackbarChannelMessage>(capacity = Int.MAX_VALUE)

    @Composable
    fun SnackbarControllerProvider(content: @Composable (snackbarHost: SnackbarHostState) -> Unit) {
    val snackHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()
    val snackController = remember(scope) { SnackbarController(snackHostState, scope) }

    DisposableEffect(snackController, scope) {
    val job = scope.launch {
    for (payload in channel) {
    snackController.showMessage(
    message = payload.message,
    duration = payload.duration,
    action = payload.action
    )
    }
    }

    onDispose {
    job.cancel()
    }
    }

    CompositionLocalProvider(LocalSnackbarController provides snackController) {
    content(
    snackHostState
    )
    }
    }

    @Immutable
    class SnackbarController(
    private val host: SnackbarHostState,
    private val scope: CoroutineScope,
    ) {
    companion object {
    val current
    @Composable
    @ReadOnlyComposable
    get() = LocalSnackbarController.current

    fun showMessage(
    message: String,
    action: SnackbarAction? = null,
    duration: SnackbarDuration = SnackbarDuration.Short,
    ) {
    channel.trySend(
    SnackbarChannelMessage(
    message = message,
    duration = duration,
    action = action
    )
    )
    }
    }


    fun showMessage(
    message: String,
    action: SnackbarAction? = null,
    duration: SnackbarDuration = SnackbarDuration.Short,
    ) {
    scope.launch {
    /**
    * note: uncomment this line if you want snackbar to be displayed immediately,
    * rather than being enqueued and waiting [duration] * current_queue_size
    */
    // host.currentSnackbarData?.dismiss()
    val result =
    host.showSnackbar(
    message = message,
    actionLabel = action?.title,
    duration = duration
    )

    if (result == SnackbarResult.ActionPerformed) {
    action?.onActionPress?.invoke()
    }
    }
    }
    }

    data class SnackbarChannelMessage(
    val message: String,
    val action: SnackbarAction?,
    val duration: SnackbarDuration = SnackbarDuration.Short,
    )


    data class SnackbarAction(val title: String, val onActionPress: () -> Unit)
    11 changes: 11 additions & 0 deletions UsageCompose.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    @Composable
    fun App() {
    SnackbarControllerProvider { host ->
    val snackbar = SnackbarController.current
    Scaffold(snackbarHost = { SnackbarHost(hostState = host) }) {
    Button(onClick = { snackbar.showMessage("hello!") }) {
    Text("Click me!")
    }
    }
    }
    }
    6 changes: 6 additions & 0 deletions UsageOutsideCompose.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    class MainActivity : ComponentActivity() {
    override fun onStart() {
    super.onStart()
    SnackbarController.showMessage("Welcome back!")
    }
    }
    1 change: 1 addition & 0 deletions description.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Code for article at [https://www.kborowy.com/blog/easy-compose-snackbar](https://www.kborowy.com/blog/easy-compose-snackbar?utm_source=github-gist&utm_medium=gist&utm_campaign=blog-post)