androidkotlinuser-interfaceandroid-jetpack-compose

Optimizing Button Size in Column space - Android Jetpack Compose


I am having problems with Button sizing on my screen. I want the buttons to be sized the same and to optimize the space I have. I tried to use spacers and Modifier.weight doesn't seem to be available anymore.

@Composable
fun ActionButton(ButtonText: String, ButtonColor: Color,) {
    Button(
        onClick = { /*TODO*/ },
        enabled = true,
        colors = ButtonColors(
            containerColor = ButtonColor,
            contentColor = Color.White,
            disabledContainerColor = Color.Gray, //TODO: Look into this later
            disabledContentColor = Color.LightGray
        ),
        modifier = Modifier
            .fillMaxWidth()
            .padding(18.dp)
    ) {
        Text(
            text = ButtonText,
            style = MaterialTheme.typography.displaySmall)
    }
}


data class ActionButtonData(val ButtonText: String, val ButtonColor: Color)

@Composable
fun ActionButtonColumn(){
    val ActionButtonsValues = listOf(
        ActionButtonData("Call 911", MaterialTheme.colorScheme.primary),
        ActionButtonData("Text 911", MaterialTheme.colorScheme.secondary),
        ActionButtonData("SOS", MaterialTheme.colorScheme.tertiary)
    )

    Column (
        horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
        modifier = Modifier
            .fillMaxSize()
    ) {
        ActionButtonsValues.forEach {
            ActionButton(ButtonText = it.ButtonText, ButtonColor = it.ButtonColor)
        }
    }
}

Solution

  • The reason why the weight modifier cannot be used in the definition of ActionButton is because it can only be used in scope of a column or row, thus it can't be used in a function because Compose doesn't "know" for sure whether the buttons will always be in or out of the column scope. You can find out by hovering your cursor over each use of Modifier.weight in your code.
    Consider the following example (a simplified version of your ActionButton definition):

    Button(
       onClick = { ... },
       modifier = modifier
          .fillMaxWidth()
          .weight(0.5f) // <-- not in a column
    ) { ... } 
    

    Here Android Studio will highlight an error, and when you hover over the use of Modifier.weight you should see the suggestion “Create extension function androidx.compose.ui.Modifier.weight”. In contrast, in this case:

    Column(
       horizontalAlignment = Alignment.CenterHorizontally,
       modifier = Modifier
          .fillMaxSize()
    ) {
       Button(modifier = Modifier.weight(1f))
    }
    

    There is no problem with using the weight modifier as we're operating in the column scope. You can make sure of that by hovering your cursor over the usage of Modifier.weight - you will see androidx.compose.foundation.layout.ColumnScope.

    Now that we've clarified what the column scope is all about - how do we actually solve this problem? We can't just move the column "context" to the ActionButton definition, right?

    Of course we can.

    The nice and simple way to ensure a specific size of elements in a column (even if they're wrapped in another function) is to add another parameter to the function definition, like this:

    @Composable
    fun ActionButton(buttonText: String, modifier: Modifier)
    

    (PS: notice I've also changed the signature of buttonText as local variables shouldn't start with uppercase letters, it's typical for classes - best practice for the future :))

    We also need to replace the original Modifier in the function body with the new one:

    Button(
       onClick = { ... },
       modifier = modifier // <-- not "Modifier"
          .fillMaxWidth()
          .padding(18.dp)
    ) {
       Text(buttonText)
    }
    

    As you may have guessed, to pass the weight information to the single ActionButton we will just put it as an argument:

    actionButtonsValues.forEach {
       ActionButton(
           buttonText = it.buttonText,
           modifier = Modifier.weight(1f)
       )
    }
    

    This way, you can not only set all buttons to equal size, but also add a weight field to ActionButtonData and make some buttons bigger than the others. You can find more about it here.