How to Create a Countdown Snackbar in Android with Jetpack Compose
Welcome 👋
In this article, we’ll create a Countdown Snackbar in Jetpack Compose. This feature is perfect for giving users a moment to rethink crucial actions, like deleting an account.
Stay tuned, and let’s dive in! 🚀
Creating the Snackbar Countdown
To begin, we define the SnackbarCountdown
composable function, which visually represents the countdown timer.
@Composable
private fun SnackbarCountdown(
timerProgress: Float,
secondsRemaining: Int,
color: Color
) {
Box(
modifier = Modifier.size(24.dp),
contentAlignment = Alignment.Center
) {
Canvas(Modifier.matchParentSize()) {
// Define the stroke
val strokeStyle = Stroke(
width = 3.dp.toPx(),
cap = StrokeCap.Round
)
// Draw the track
drawCircle(
color = color.copy(alpha = 0.12f),
style = strokeStyle
)
// Draw the progress
drawArc(
color = color,
startAngle = -90f,
sweepAngle = (-360f * timerProgress),
useCenter = false,
style = strokeStyle
)
}
// Display the remaining seconds
Text(
text = secondsRemaining.toString(),
style = LocalTextStyle.current.copy(
fontSize = 14.sp,
color = color
)
)
}
}
Setting Up the CountdownSnackbar Function
Now that we have the SnackbarCountdown
composable, we can define the CountdownSnackbar
composable, which takes several parameters to customize its appearance and behavior.
@Composable
fun CountdownSnackbar(
snackbarData: SnackbarData,
modifier: Modifier = Modifier,
durationInSeconds: Int = 5,
actionOnNewLine: Boolean = false,
shape: Shape = SnackbarDefaults.shape,
containerColor: Color = SnackbarDefaults.color,
contentColor: Color = SnackbarDefaults.contentColor,
actionColor: Color = SnackbarDefaults.actionColor,
actionContentColor: Color = SnackbarDefaults.actionContentColor,
dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
) {
// Implementation here...
}
⏳ snackbarData ➜ Data for the snackbar.
⏳ modifier ➜ Modifier to be applied to the snackbar.
⏳ durationInSeconds ➜ Duration of the countdown timer.
⏳ actionOnNewLine ➜ Whether to display the action on a separate line.
⏳ shape ➜ The shape of the snackbar’s container.
⏳ containerColor, contentColor, actionColor, actionContentColor, dismissActionContentColor ➜ Various color styling parameters.
CountdownSnackbar Implementation
Managing Snackbar Duration and State
Next, we calculate the total duration in milliseconds and manage the remaining time using a state variable. We also use a LaunchedEffect
to handle the countdown and dismiss the snackbar when time runs out.
val totalDuration = remember(durationInSeconds) { durationInSeconds * 1000 }
var millisRemaining by remember { mutableIntStateOf(totalDuration) }
LaunchedEffect(snackbarData) {
while (millisRemaining > 0) {
delay(40)
millisRemaining -= 40
}
snackbarData.dismiss()
}
Using a 40-millisecond interval results in a 25 FPS progress update, which is quite smooth for the human eye. Feel free to adjust it to suit your specific needs.
Handling Action & Dismiss Buttons
To create the action
and dismiss
buttons, we utilize the information provided by snackbarData
.
// Define the action button if an action label is provided
val actionLabel = snackbarData.visuals.actionLabel
val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
@Composable {
TextButton(
colors = ButtonDefaults.textButtonColors(contentColor = actionColor),
onClick = { snackbarData.performAction() },
content = { Text(actionLabel) }
)
}
} else {
null
}
// Define the dismiss button if the snackbar includes a dismiss action
val dismissActionComposable: (@Composable () -> Unit)? = if (snackbarData.visuals.withDismissAction) {
@Composable {
IconButton(
onClick = { snackbarData.dismiss() },
content = {
Icon(Icons.Rounded.Close, null)
}
)
}
} else {
null
}
Displaying the Snackbar
Finally, let’s put everything together and display the snackbar.
Snackbar(
modifier = modifier.padding(12.dp), // Apply padding around the snackbar
action = actionComposable,
actionOnNewLine = actionOnNewLine,
dismissAction = dismissActionComposable,
dismissActionContentColor = dismissActionContentColor,
actionContentColor = actionContentColor,
containerColor = containerColor,
contentColor = contentColor,
shape = shape,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
SnackbarCountdown(
// Calculate the progress of the timer
timerProgress = millisRemaining.toFloat() / totalDuration.toFloat(),
// Calculate the remaining seconds
secondsRemaining = (millisRemaining / 1000) + 1,
color = contentColor
)
// Display the message
Text(snackbarData.visuals.message)
}
}
Congratulations🥳! We’ve successfully built it👏. For the complete code implementation, you can access it on GitHub Gist🧑💻. Now, let’s explore how we can put it to use.
Advertisement
Are you learning a foreign language and struggling with new vocabulary? Then, I strongly recommend you check out this words-learning app, which will make your journey easy and convenient!
Practical Example 💁
Let’s build a practical example where the user deletes an account and has 5 seconds to undo this crucial action.
Box(Modifier.fillMaxSize()) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
// Define a SnackbarHostState to manage the state of the snackbar
val snackbarHostState = remember { SnackbarHostState() }
Button(
modifier = Modifier.align(Alignment.Center),
onClick = {
scope.launch {
// Show a snackbar
val result = snackbarHostState.showSnackbar(
message = "User account deleted.",
actionLabel = "UNDO",
duration = SnackbarDuration.Indefinite
)
// Handle the snackbar result
when (result) {
SnackbarResult.Dismissed -> {
Toast.makeText(context, "Deleted permanently", Toast.LENGTH_SHORT).show()
}
SnackbarResult.ActionPerformed -> {
Toast.makeText(context, "Deletion canceled", Toast.LENGTH_SHORT).show()
}
}
}
}
) {
Text("Delete Account")
}
// Create a SnackbarHost to display the snackbar
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(BottomCenter)
) { data ->
// Use the CountdownSnackbar
CountdownSnackbar(data)
}
}
The output:
You might also like 👇
Thank you for reading this article! ❤️ I hope you’ve found it enjoyable and valuable. Feel free to show your appreciation by hitting the clap 👏 if you liked it and follow Kappdev for more exciting articles 😊
Happy coding!