Skip to content

About

Implementation of the "One-Time Event" pattern for Android and Kotlin Multiplatform

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

36 Commits

Folders and files

Repository files navigation

Implementation of the "One-Time Event" pattern for Android and Kotlin Multiplatform.
Typical usage is ViewModel with Jetpack Compose / Compose Multiplatform.

History of the question and other solutions

LiveData-based solutions

Outdated as of 2025.

Kotlin Channels

Solutions based on Channels have a limitation: they must operate on Main / Main.immediate scheduler to be reliable.
More information:

ViewModel: One-off event antipatterns

A Channel doesn’t guarantee the delivery and processing of the events. Therefore, events can be lost, leaving the UI in an inconsistent state.

The idea about Main.immediate

The most trivial solution is to rely on the specifics of how view lifecycle works in Android and on Dispatchers.Main.immediate.

ViewModel: Events as State are an Antipattern

Exposing events from the VM does not mean that the VM is not the source of truth.

State + notify about consumption

It works but requires additional code which can be tedious.
Google's approach (explicitly consume events)

UI needs to notify the ViewModel to trigger another state update when the message has been shown on the screen.

SingleEventStream (this soultion, middle ground)

Key features:

  • It is guaranteed that a pushed even will not be lost.
  • Many events may be queued until someone collects them.
  • New event can be pushed from any thread / dispatcher.
  • Collection may be happening from any thread / dispatcher.
  • Order of events is not guaranteed if pushed from multiple threads / dispatchers.

Limitations:

  • Only 1 collector at a time is supported.

How and why it works:
Events are stored in the internal queue. Any access to the queue happens under the Mutex. Any event that was not consumed waits in the queue. Collector automatically and atomically consumes the queue. It's partially similar to what Google recommends (notify about the consumption), but automatic.

Usage example

The library is not published. Basically, it's just 1 class, so you can copy it to your project.

Code

Tests

Typically in ViewModel:

private val _events = SingleEventStream<Any>(viewModelScope)
val events = _events.asReadOnly()

fun onClick() {
    _events.push("Something")
}

Typically in a Composable:

val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(Unit) {
    lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.events.collect {
            // Handle the events here
        }
    }
}

About

Implementation of the "One-Time Event" pattern for Android and Kotlin Multiplatform

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages