androidkotlinandroid-jetpack-composeandroid-jetpackkotlin-flow

@Composable invocations can only happen from the context of a @Composable function in observers/collect in Jetpack Compose


I'm new to Jetpack Compose and I'm facing an issue with implementing a button click. My scenario is as follows: I need to call an API when a button is clicked and update the UI once the response is received. While the API call is happening, I need to show a loader. However, I'm encountering the error "@Composable invocations can only happen from the context of a @Composable function." Below is a screenshot of my issue.

enter image description here

Any help would be deeply appreciated. Thanks in advance


Solution


  • if you check Button component document you will see onClick parameter is not allow composable lamda like onClick: @Composable () -> Unit use a mutableState variable for show loading bar:

    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.setValue
    
    var loading: Boolean by remember{ mutableStateOf(false) }    
    Button(
            enabled = enabled,
            modifier = Modifier.fillMaxWidth(),
            onClick = { if (!loading) onClick() },
            colors = colors
        ) {
            Row(
                Modifier,
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Center
            ) {
                if (loading) {
                    CircularProgressIndicator(
                        color = colors.contentColor,
                        modifier = Modifier
                            .size(30.dp)
                            .progressSemantics()
                    )
                } else {
                    Text(
                        modifier = Modifier.fillMaxWidth(),
                        textAlign = TextAlign.Center,
                        text = text, // write your text
                        maxLines = 1
                    )
                }
            }
    

    My advice create a function for it for Example:

    fun PrimaryButton(
        modifier: Modifier = Modifier,
        loadingSize: Dp = 32.dp,
        enabled: Boolean = true,
        loading: Boolean = false,
        colors: ButtonColors = ButtonDefaults.buttonColors().copy(
            contentColor = Colors.White,
            disabledContainerColor = Color.Gray,
            containerColor = Color.Blue,
            disabledContentColor = Color.White
        ),
        text: String,
        onClick: () -> Unit,
        ) {
        Button(
            enabled = enabled,
            modifier = Modifier
                .fillMaxWidth()
                .then(modifier),
            onClick = {
                if (!loading)
                    onClick()
            },
            colors = colors
        ) {
            Row(
                Modifier,
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.Center
            ) {
                if (loading) {
                    CircularProgressIndicator(
                        color = colors.contentColor,
                        modifier = Modifier
                            .size(loadingSize)
                            .progressSemantics()
                    )
                } else {
                    Text(
                        modifier = Modifier.fillMaxWidth(),
                        textAlign = TextAlign.Center,
                        text = text,
                        maxLines = 1
                    )
                }
            }
    
        }
    }
    
    @Preview
    @Composable
    fun PrimaryButtonPreview() {
        PrimaryButton(modifier = Modifier, text = "Button") {}
    }