
Lifecycle-aware Snackbar library streamlines Snackbar management, preventing missed or duplicated messages. Offers one-liner API, automatic string resource conversion, and full unit-testability.
A lifecycle-aware Snackbar library that eliminates boilerplate and prevents missed/duplicated snackbars in KMP/CMP.
⚠️ Using Jetpack Compose for Android only?
This library relies onStringResourceandgetStringfromorg.jetbrains.compose.resources, which are not supported in pure Android projects. Please refer to the Android-specific version instead: AndroidSnackbarStateFlowHandle.
ViewModel
commonMain.dependencies {
implementation("io.github.aungthiha:snackbar-stateflow-handle:1.0.0")
}import androidx.lifecycle.ViewModel
import io.github.aungthiha.snackbar.SnackbarStateFlowHandle
import io.github.aungthiha.snackbar.SnackbarStateFlowOwner
// import {your package}.generated.resources.Res
class MyViewModel(
private val snackbarStateFlowHandle: SnackbarStateFlowHandle = SnackbarStateFlowHandle()
) : ViewModel(), SnackbarStateFlowOwner by snackbarStateFlowHandle {
fun showSimpleSnackbar() {
showSnackBar(message = Res.string.hello_world)
}
}import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import io.github.aungthiha.snackbar.observeWithLifecycle
import io.github.aungthiha.snackbar.showSnackbar
@Composable
fun MyScreen(
// yes, it's a bad practice to directly pass a ViewModel into a Screen
// but this is to make it easy to show how to use the SnackbarStateFlowHandle
viewModel: MyViewModel = viewModel()
) {
val snackbarHostState = remember { SnackbarHostState() }
viewModel.snackbarStateFlow.observeWithLifecycle {
snackbarHostState.showSnackbar(it)
}
Scaffold(snackbarHost = { SnackbarHost(hostState = snackbarHostState) }) {
// ... UI content
}
}Use showSnackBar(...) from your ViewModel. You can pass string resources, string literals, or even mix both.
// All parameters
showSnackBar(
message = Res.string.hello_world, // can be either string resource or String
actionLabel = "ok", // can be either string resource or String
duration = SnackbarDuration.Indefinite,
onActionPerform = { /* handle action */ },
onDismiss = { /* handle dismiss */ }
)
// Using a string resource
showSnackBar(
message = Res.string.hello_world,
actionLabel = Res.string.ok
)
// Using a raw string (e.g., from backend or dynamic input)
showSnackBar(
message = "Something went wrong!",
actionLabel = "Retry"
)
// Mixing string types
showSnackBar(
message = "မင်္ဂလာပါ",
actionLabel = Res.string.ok
)
showSnackBar(
message = Res.string.hello_world,
actionLabel = "ok"
)All parameters are optional except the message.
For more example usages, see
composeApp/src/commonMain/kotlin/io/github/aungthiha/snackbar/demo/AppViewModel.kt.
Use kotlin.test with runTest and collect from snackbarStateFlow.
class MyViewModelTest {
private val viewModel = MyViewModel()
@Test
fun snackbar_is_emitted() = runTest {
viewModel.showSimpleSnackbar()
val emitted = viewModel.snackbarStateFlow.first() // List<SnackbarModel>
assertEquals(
SnackbarString(Res.string.hello_world),
emitted.first().message
)
}
}Tested with:
(Other targets are available but not tested yet)
PRs and feedback welcome!
MIT
A lifecycle-aware Snackbar library that eliminates boilerplate and prevents missed/duplicated snackbars in KMP/CMP.
⚠️ Using Jetpack Compose for Android only?
This library relies onStringResourceandgetStringfromorg.jetbrains.compose.resources, which are not supported in pure Android projects. Please refer to the Android-specific version instead: AndroidSnackbarStateFlowHandle.
ViewModel
commonMain.dependencies {
implementation("io.github.aungthiha:snackbar-stateflow-handle:1.0.0")
}import androidx.lifecycle.ViewModel
import io.github.aungthiha.snackbar.SnackbarStateFlowHandle
import io.github.aungthiha.snackbar.SnackbarStateFlowOwner
// import {your package}.generated.resources.Res
class MyViewModel(
private val snackbarStateFlowHandle: SnackbarStateFlowHandle = SnackbarStateFlowHandle()
) : ViewModel(), SnackbarStateFlowOwner by snackbarStateFlowHandle {
fun showSimpleSnackbar() {
showSnackBar(message = Res.string.hello_world)
}
}import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import io.github.aungthiha.snackbar.observeWithLifecycle
import io.github.aungthiha.snackbar.showSnackbar
@Composable
fun MyScreen(
// yes, it's a bad practice to directly pass a ViewModel into a Screen
// but this is to make it easy to show how to use the SnackbarStateFlowHandle
viewModel: MyViewModel = viewModel()
) {
val snackbarHostState = remember { SnackbarHostState() }
viewModel.snackbarStateFlow.observeWithLifecycle {
snackbarHostState.showSnackbar(it)
}
Scaffold(snackbarHost = { SnackbarHost(hostState = snackbarHostState) }) {
// ... UI content
}
}Use showSnackBar(...) from your ViewModel. You can pass string resources, string literals, or even mix both.
// All parameters
showSnackBar(
message = Res.string.hello_world, // can be either string resource or String
actionLabel = "ok", // can be either string resource or String
duration = SnackbarDuration.Indefinite,
onActionPerform = { /* handle action */ },
onDismiss = { /* handle dismiss */ }
)
// Using a string resource
showSnackBar(
message = Res.string.hello_world,
actionLabel = Res.string.ok
)
// Using a raw string (e.g., from backend or dynamic input)
showSnackBar(
message = "Something went wrong!",
actionLabel = "Retry"
)
// Mixing string types
showSnackBar(
message = "မင်္ဂလာပါ",
actionLabel = Res.string.ok
)
showSnackBar(
message = Res.string.hello_world,
actionLabel = "ok"
)All parameters are optional except the message.
For more example usages, see
composeApp/src/commonMain/kotlin/io/github/aungthiha/snackbar/demo/AppViewModel.kt.
Use kotlin.test with runTest and collect from snackbarStateFlow.
class MyViewModelTest {
private val viewModel = MyViewModel()
@Test
fun snackbar_is_emitted() = runTest {
viewModel.showSimpleSnackbar()
val emitted = viewModel.snackbarStateFlow.first() // List<SnackbarModel>
assertEquals(
SnackbarString(Res.string.hello_world),
emitted.first().message
)
}
}Tested with:
(Other targets are available but not tested yet)
PRs and feedback welcome!
MIT