Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Compose with single event

Right now I have an Event class in the ViewModel that is exposed as a Flow this way:

abstract class BaseViewModel() : ViewModel() {

    ...

    private val eventChannel = Channel<Event>(Channel.BUFFERED)
    val eventsFlow = eventChannel.receiveAsFlow()

    fun sendEvent(event: Event) {
        viewModelScope.launch {
            eventChannel.send(event)
        }
    }

    sealed class Event {
        data class NavigateTo(val destination: Int): Event()
        data class ShowSnackbarResource(val resource: Int): Event()
        data class ShowSnackbarString(val message: String): Event()
    }
}

And this is the composable managing it:

@Composable
fun SearchScreen(
    viewModel: SearchViewModel
) {
    val events = viewModel.eventsFlow.collectAsState(initial = null)
    val snackbarHostState = remember { SnackbarHostState() }
    val coroutineScope = rememberCoroutineScope()
    Box(
        modifier = Modifier
            .fillMaxHeight()
            .fillMaxWidth()
    ) {
        Column(
            modifier = Modifier
                .padding(all = 24.dp)
        ) {
            SearchHeader(viewModel = viewModel)
            SearchContent(
                viewModel = viewModel,
                modifier = Modifier.padding(top = 24.dp)
            )
            when(events.value) {
                is NavigateTo -> TODO()
                is ShowSnackbarResource -> {
                    val resources = LocalContext.current.resources
                    val message = (events.value as ShowSnackbarResource).resource
                    coroutineScope.launch {
                        snackbarHostState.showSnackbar(
                            message = resources.getString(message)
                        )
                    }
                }
                is ShowSnackbarString -> {
                    coroutineScope.launch {
                        snackbarHostState.showSnackbar(
                            message = (events.value as ShowSnackbarString).message
                        )
                    }
                }
            }
        }
        SnackbarHost(
            hostState = snackbarHostState,
            modifier = Modifier.align(Alignment.BottomCenter)
        )
    }
}

I followed the pattern for single events with Flow from here.

My problem is, the event is handled correctly only the first time (SnackBar is shown correctly). But after that, seems like the events are not collected anymore. At least until I leave the screen and come back. And in that case, all events are triggered consecutively.

Can't see what I'm doing wrong. When debugged, events are sent to the Channel correctly, but seems like the state value is not updated in the composable.

like image 250
Duqe Avatar asked Jan 20 '26 07:01

Duqe


1 Answers

Rather than placing your logic right inside composable place them inside

// Runs only on initial composition 
LaunchedEffect(key1 = Unit) {
  viewModel.eventsFlow.collectLatest { value -> 
    when(value) {
       // Handle events
    }
 }
}

And also rather than using it as state just collect value from flow in LaunchedEffect block. This is how I implemented single event in my application

like image 157
Jagadeesh K Avatar answered Jan 22 '26 19:01

Jagadeesh K



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!