Search code examples
androidandroid-jetpack-composeandroid-jetpack

How to show loader on a dynamic button in jetpack compose


So the issue I'm facing is I have a button which can have dynamic text, button have a state isLoading which when turned true, View is recomposed to show loader. Now Loader have its own width & height. How do I manage the loader to follow the same dimensions as of the text which was shown earlier. Right now when loader appears the button size grow large & after the loading is complete & isLoading is false again, the button would come to its actually size with text inside it.

Edit 1: The approach suggested by @thracian is working fine, other than a weird issue where the button gets some predefined height (& so resulting in same value while calculating the view height), which makes the button looks taller than what I'm planning to implement. Attaching code for ref, how do I make this button follow wrapped content & at the same time get the same calculated value in view size as well.

@Composable
fun GreenCTA(
    modifier: Modifier = Modifier, onClick: () -> Unit, title: String, isShowLoader: Boolean
) {
    var buttonSize by remember {
        mutableStateOf(DpSize.Zero)
    }
    val density = LocalDensity.current
    Button(
        modifier = modifier
            .then(if (buttonSize != DpSize.Zero) modifier.size(buttonSize) else modifier)
            .onSizeChanged { newSize ->
                if (buttonSize == DpSize.Zero) {
                    buttonSize = with(density) {
                        newSize
                            .toSize()
                            .toDpSize()
                    }
                }
            },
        onClick = { onClick.invoke() },
        border = BorderStroke(1.dp, color = colorResource(id = R.color.pass_color_c2e6cd)),
        colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(id = R.color.pass_color_e3f6ec)),
        shape = RoundedCornerShape(6.dp),
        contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
        elevation = ButtonDefaults.elevation(0.dp),
    ) {
        if (isShowLoader) BlueLoader(
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(3f)
        )
        else {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Image(
                    painter = painterResource(id = R.drawable.ic_add_tag),
                    contentDescription = "Button Logo"
                )
                Text(
                    text = title,
                    style = MaterialTheme.typography.body2.copy(lineHeight = 20.sp),
                    fontWeight = FontWeight.SemiBold,
                )
            }
        }
    }
}

Solution

  • If you wish to adjust size of the button based on Text which is composed initially you can use Modifier.onSizeChanged for Button dimensions while Text is displayed and then set these dimensions as size to button while Text is out of Composition such as

    Preview
    @Composable
    private fun Test() {
    
        var isLoading by remember { mutableStateOf(false) }
        var buttonSize by remember { mutableStateOf(DpSize.Zero) }
        val density = LocalDensity.current
    
        OutlinedButton(
            modifier = Modifier
                .then(
                    if (buttonSize != DpSize.Zero) Modifier.size(buttonSize) else Modifier
                )
                .onSizeChanged { newSize ->
                    if (buttonSize == DpSize.Zero) {
                        buttonSize = with(density) {
                            newSize
                                .toSize()
                                .toDpSize()
                        }
                    }
                },
            onClick = { isLoading = isLoading.not() }) {
            if (isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier
                        .fillMaxHeight()
                        .aspectRatio(1f)
                )
            } else {
                Text(text = "Click me")
            }
        }
    }
    

    Also if you wish to adjust size of a Button to a Composable that is not composed yet, for instance loader size, you can use SubcomposeLayout to get dimensions beforehand as in this answer

    How to adjust size of component to it's child and remain unchanged when it's child size will change? (Jetpack Compose)