Reputation: 183
I am new to jetpack compose and is trying to show an error snackbar whenever the error message I am observing is not null.
Scaffold(scaffoldState = scaffoldState) {
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
}
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
//some ui components inside here
}
}
The issue in the above code is that, the first time the error message changes from null to a particular message it appears fine. However on a repeated user action that produces the same error message it's not coming again.
P.S - I know this is happening due to placing the errorMessage
as key inside the LaunchedEffect
. My doubt is that, is there a different approach to achieve what I want?
Upvotes: 4
Views: 2219
Reputation: 1957
Dirty hack:
So the idea is to restart the side-effect which in this case is LaunchedEffect
Append your error message with Unix timestamp and then remove the time stamp while displaying the error message in the snack bar.
something like this:
errorMessage = errorMessage + System.currentTimeMillis()
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
val messageToDisplay = //code to remove the timepstamp
scaffoldState.snackbarHostState.showSnackbar(messageToDisplay)
}
}
Upvotes: 0
Reputation: 3097
My solution is to support repeated messages/errors and don't show one message/error multiple times. Recomposition will be called just ones
1.Create a smart delegate that removes the value after reading. It is the main logic
class OnceReadDelegate<T>(private var value: T? = null) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
val currentValue = value
value = null
return currentValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
value = newValue
}
}
2.Create a Message class that manages our text and type of message
class Message {
var payload by OnceReadDelegate<String?>()
var type: MessageType = MessageType.Error
companion object {
fun create(text: String?, messageType: MessageType = MessageType.Error) =
Message().apply {
payload = text
this.type = messageType
}
}
}
sealed class MessageType {
data object Regular : MessageType()
data object Error : MessageType()
}
3.My example of state class with our Message class
data class SettingsPageState(
val listWidgetState: List<WidgetViewState> = emptyList(),
val listAgeFormat: List<AgeFormat> = emptyList(),
val message: Message? = null,
val isLoading: Boolean = true,
val isEmpty: Boolean = false
)
4.My example of updatin the state with a new message
mutableState.update { previousState ->
previousState.copy(message = Message.create(exception?.message))
}
5.And finally Composable function how to how Snackbar
@Composable
fun SettingsScreen() {
...
LaunchedEffect(state.message) {
val text = state.message?.payload
if (!text.isNullOrEmpty()) {
//your method to show snackbar
onShowSnackbar(text , null)
}
}
...
}
It is all)
Upvotes: 0
Reputation: 13159
I see some issues with the answer provided above
First, there is no need of relaunching a coroutine inside a LaunchedEffect
(which will execute a suspend function also) , so we can remove the scope.launch
from the LaunchedEffect
Second, if we use resetErrorMessage()
before .showSnackBar
happens, we will emit a null (empty) message. We should reset the error message after the showSnackbar
has been executed.
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
resetErrorMessage() // reset errorMessage
}
}
Upvotes: 1
Reputation: 24044
This is happening because the LaunchedEffect
will run again just in case the errorMessage
has changed.
What you can do is:
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
resetErrorMessage() // reset errorMessage
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
}
}
}
The resetErrorMessage
must set the errorMessage
to null, so the LaunchedEffect
will run again, but since you're checking if it is not null, nothing will happen. But as soon you receive a new error message, the LaunchedEffect
will be executed again.
Upvotes: 6