package de.halfbit.seventysix import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.jetbrains.compose.ui.tooling.preview.Preview import kotlin.time.Duration.Companion.seconds @Composable fun App(viewModel: AppViewModel) { MaterialTheme { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { val state = viewModel.state() Button(onClick = state.onLoadRss) { Text("Load RSS") } if (state.loading) Text("Loading...") if (state.rssResult.isNotEmpty()) Text(state.rssResult) } } } @Preview @Composable fun PreviewApp() { App( viewModel = object : AppViewModel { @Composable override fun state(): State = State( loading = true, rssResult = "", onLoadRss = {}, ) } ) } data class State( val loading: Boolean, val rssResult: String, val onLoadRss: () -> Unit, ) sealed interface Event { data object LoadRss : Event } interface AppViewModel { @Composable fun state(): State } class DefaultAppViewModel( private val fetchRss: suspend () -> String = { delay(3.seconds); "RSS data is here" }, ) : AppViewModel { @Composable override fun state(): State { var loading by remember { mutableStateOf(false) } var rssResult by remember { mutableStateOf("") } val onLoadRes = { post(Event.LoadRss) } onEvent { event -> when (event) { Event.LoadRss -> { if (loading) return@onEvent loading = true rssResult = "" launch { rssResult = fetchRss() loading = false } } } } return State( loading = loading, rssResult = rssResult, onLoadRss = onLoadRes, ) } private val events = MutableSharedFlow(extraBufferCapacity = 20) @Composable private inline fun onEvent(crossinline block: CoroutineScope.(event: Event) -> Unit) { LaunchedEffect(Unit) { events.collect { event -> block(event) } } } private fun post(event: Event) { if (!events.tryEmit(event)) { error("Buffer overflow") } } }