The entity to handle one-time viewModel events.
Add the dependency:
repositories {
mavenCentral()
google()
}
dependencies {
implementation("com.redmadrobot.viewmodelevents:viewmodelevents-flow:<version>")
// or
implementation("com.redmadrobot.viewmodelevents:viewmodelevents-livedata:<version>")
// Compose extensions
implementation("com.redmadrobot.viewmodelevents:viewmodelevents-compose:<version>")
}
One-time events (or single events) are a common pattern to display messages or errors in UI.
ViewModelEvents
addresses the challenge of buffering and consuming one-time events:
- Buffering: When there are no subscribers to
ViewModelEvents
, emitted events are stored in a buffer. All buffered events are then delivered sequentially as soon as you subscribe to the ViewModelEvents - Consumption: Each event is emitted only once. Thus, if you re-subscribe to the ViewModelEvents, you will not receive any events that have already been consumed.
There are two implementations: via flow (recommended for use) and via livedata (deprecated).
This implementation utilizes StateFlow
under the hood and provides the flow
field to observe events.
data class MessageEvent(val message: String) : Event
val viewModelEvents = ViewModelEvents()
viewModelEvents.offerEvent(MessageEvent("A"))
viewModelEvents.offerEvent(MessageEvent("B"))
// Observe
events.flow
.onEach { onEvent(it) }
.launchIn(scope)
viewModelEvents.offerEvent(MessageEvent("C"))
MessageEvent(message="A")
MessageEvent(message="B")
MessageEvent(message="C")
ViewModelEvents
can also be used with Jetpack Compose:
// Remember to add viewmodelevents-compose dependency
@Composable
public fun Screen() {
// We assume, ViewModel implementation has `events` field providing ViewModelEvents instance
val viewModel = viewModel<FeatureViewModel>()
ViewModelEventsEffect(viewModel.events) { event: Event ->
when(event) {
is MessageEvent -> println("Message: $event")
is ErrorMessageEvent -> println("Error: $event")
}
}
}
That subscription emits values from ViewModelEvents
when the lifecycle is at least at minActiveState
(Lifecycle.State.STARTED
by default).
Emission stops when the lifecycle state falls below the minActiveState
state.
This implementation utilizes LiveData
under the hood and provides methods to observe events on the given lifecycle.
data class MessageEvent(val message: String) : Event
val viewModelEvents = ViewModelEvents()
viewModelEvents.offerEvent(MessageEvent("A"))
viewModelEvents.offerEvent(MessageEvent("B"))
viewModelEvents.observeForever { println(it) }
viewModelEvents.offerEvent(MessageEvent("C"))
MessageEvent(message=A)
MessageEvent(message=B)
MessageEvent(message=C)
Extension | Description |
---|---|
Fragment.observe(liveData: ViewModelEvents, onEvent: (Event) -> Unit) |
Shorter way to observe LiveData in a fragment |
ComponentActivity.observe(liveData: ViewModelEvents, onEvent: (Event) -> Unit) |
Shorter way to observe LiveData in an activity |
Here you can find the patterns that we found useful to simplify usage of ViewModelEvents
.
To simplify events sending, it is useful to declare the following interface:
/** Interface for ViewModel events dispatching. */
public interface EventsDispatcher {
public val events: ViewModelEvents
/** Offers the given [event] to be added to the ViewModel events. */
public fun offerEvent(event: Event) {
events.offerEvent(event)
}
}
That's it!
You can add this interface to any class you want to be able to send events.
For example, to your base ViewModel
:
abstract class BaseViewModel : ViewModel(), EventsDispatcher {
override val events = ViewModelEvents()
}
Thus, all ViewModel
s will be able to use offerEvent(Event)
to send events:
class FeatureViewModel : BaseViewModel() {
fun onError(message: String) = offerEvent(ErrorMessageEvent(message))
}
It is useful to create extension-functions to send common events. Let's imagine you have the following events:
data class MessageEvent(val message: String) : Event
data class ErrorMessageEvent(val message: String) : Event
To simplify sending of these events, you can create shortcuts.
It works best with the EventsDispatcher
interface.
fun EventsDispatcher.showMessage(message: String) {
offerEvent(MessageEvent(message))
}
fun EventsDispatcher.showError(message: String) {
offerEvent(ErrorMessageEvent(message))
}
Merge requests are welcome. For major changes, please open an issue first to discuss what you would like to change.