Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/leonard-palm/compose-state-events

A new way to implement One-Time-UI-Events (former SingleLiveEvent) in a Compose world.
https://github.com/leonard-palm/compose-state-events

android android-development android-library compose-ui jetpack-android jetpack-compose kotlin kotlin-android state-management

Last synced: 9 days ago
JSON representation

A new way to implement One-Time-UI-Events (former SingleLiveEvent) in a Compose world.

Awesome Lists containing this project

README

        

![JitPack](https://img.shields.io/jitpack/version/com.github.leonard-palm/compose-state-events?color=%23%233cdb83&style=for-the-badge)
![GitHub](https://img.shields.io/github/license/leonard-palm/compose-state-events?color=%234185f3&style=for-the-badge)
![GitHub top language](https://img.shields.io/github/languages/top/leonard-palm/compose-state-events?color=%237f52ff&style=for-the-badge)






Compose-State-Events

A new way to implement One-Time-UI-Events (former SingleLiveEvent) in a Compose world.

This library will help you to avoid implementing any antipatterns regarding One-Time-UI-Events as despribed by Manuel Vivo's [article](https://medium.com/androiddevelopers/viewmodel-one-off-event-antipatterns-16a1da869b95).

See the samples below on how to effectively use `StateEvent` in your view's state and `EventEffect` in your composables.

To get an in depth idea on how to migrate see this [article](https://medium.com/proandroiddev/how-to-handle-viewmodel-one-time-events-in-jetpack-compose-a01af0678b76) from Yanneck Reiß.

# How to use

> Imagine a simple usecase where you need to fetch some list data from an API and display the result on the screen.

### View State Object
```kotlin
data class FlowerViewState(
val flowers: List = emptyList(),
val isLoadingFlowers: Boolean = false,
val downloadSucceededEvent: StateEvent = consumed,
val downloadFailedEvent: StateEventWithContent = consumed()
)
```
> Imagine we would like to show a green success snackbar or a red failure snackbar after the loading has finished.
These two events would be represented with the two new fields `downloadSucceededEvent` and `downloadFailedEvent` in our view state object.

Use the `StateEventWithContent` when you need to pass some data to the consumer (the composable).
In the example above a StringRes with a fitting error description is passed.

### ViewModel
```kotlin
private val _stateStream = MutableStateFlow(FlowerViewState())
val stateStream = _stateStream.asStateFlow()

private var state: FlowerViewState
get() = _stateStream.value
set(newState) {
_stateStream.update { newState }
}

fun loadFlowers(){
viewModelScope.launch {
state = state.copy(isLoading = true)
state = when (val apiResult = loadAllFlowersFromApiUseCase.call()) {
is Success -> state.copy(flowers = apiResult, downloadSucceededEvent = triggered)
is Failure -> state.copy(downloadFailedEvent = triggered(R.string.error_load_flowers))
}
state = state.copy(isLoading = false)
}
}

fun onConsumedDownloadSucceededEvent(){
state = state.copy(downloadSucceededEvent = consumed)
}

fun onConsumedDownloadFailedEvent(){
state = state.copy(downloadFailedEvent = consumed())
}
```
To trigger an event without any data just use the `triggered` value, otherwise use the `triggered(content: T)` function.
To consume an event without any data just use the `consumed` value, otherwise use the `consumed()` function.

### Composable

```kotlin
val viewModel: MainViewModel = viewModel()
val viewState: MainViewState by viewModel.stateStream.collectAsStateLifecycleAware()

EventEffect(
event = viewState.downloadSucceededEvent,
onConsumed = viewModel::onConsumedDownloadSucceededEvent
) {
scaffoldState.snackbarHostState.showSnackbar("Download succeeded.")
}

EventEffect(
event = viewState.downloadFailedEvent,
onConsumed = viewModel::onConsumedDownloadFailedEvent
) { stringRes ->
scaffoldState.snackbarHostState.showSnackbar(context.resources.getString(stringRes))
}
```
The `EventEffect` is a `LaunchedEffect` that will be executed, when the event is in its triggered state.
When the event action was executed the effect calls the passed `onConsumed` callback to force you to set the view state field to be consumed.

### Special Case: Navigation
In the regular way you only want to consume your event when it really got processed. However, in some special cases you want to make sure that the `StateEvent` gets consumed no matter what the outcome of your code that gets triggered will be.
One case in which that gets relevant is when you want to navigate to another screen, if a `StateEvent` or `StateEventWithContent` got invoked.

For this special case, instead of `EventEffect`, you can use the `NavigationEventEffect`.

```kotlin
NavigationEventEffect(
event = viewState.downloadFailedEvent,
onConsumed = viewModel::onConsumedDownloadFailedEvent
) {
navigator.navigateBack()
}
```

# Installation

```gradle
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
dependencies {
implementation 'com.github.leonard-palm:compose-state-events:2.2.0'
}
```