Exploring Jetpack Compose: Radio Group

E

In the last section we looked at the Radio Button, allowing us to create a single selectable item. However, defining a group of these would require a collection of boilerplate logic as we’d have to manually handle the state for the group of buttons. When it comes to this scenario, Jetpack Compose contains a Radio Group which can be used to create a collection of radio buttons for single selection. When it comes to the Radio Group, there are two functions which can be used to compose an instance. Lets dive into the first of the two:

@Composable
fun RadioGroup(
    options: List<String>,
    selectedOption: String?,
    onSelectedChange: (String) -> Unit,
    radioColor: Color = MaterialTheme.colors.secondary,
    textStyle: TextStyle? = null
)

As we can see here, there are five available properties that we can pass to the Radio Group function:

  • options – a list of strings representing the options to be displayed within the Radio Group. This is required
  • selectedOption – the selected option out of the given option list. If this is null then no option will be represented as selected. This is required
  • onSelectedChange – a lambda used to receive callback events for when the selection changes. This is required
  • radioColor – the color to be used for the radio items. If not provided, then the secondary color from the application theme will be applied
  • textStyle – the style to be applied to the text labels for the radio items

With the above in mind we can create a simple Radio Group by providing the required values from the above list. These are the options, selectedOption and onSelectedChange values:

val options = listOf("one", "two", "three")
RadioGroup(
    options = options,
    selectedOption = null,
    onSelectedChange = { }
)

As we can see above, this gives us a radio group with buttons labelled by the options we have provided. However, when you interact with the above none of the options are actually selectable. That’s because we haven’t provided any of the required logic for handling the currently selected option in our group. We’ll begin here by creating a new Model reference to hold the state of our currently selected option:

@Model
class RadioState(var selectedOption: String? = null)

Now that we have this in place, we can pass it to our composable function. We’ll begin by assigning a value for our groups selectedOption. For this we just need to utilise the selectedOption string in our formState reference. On first run this will be null – whilst this is acceptable for a RadioGroup, in this case we’ll want an option to be selected by default. So if our selectedOption is null then we’ll fall back to the first item in our options list. If we don’t set this default, then no option will be selected by default in our radio group.

Secondly we need to handle when the selection changes in our radio group. Within our call back we need to update the selectedOption value in our model. For these we’ll just use the value provided in the callback and when set, our radio group will be updated to reflect this change.

@Composable
fun SimpleGroupComponent(formState: RadioState) {
    val options = listOf("one", "two", "three")
    RadioGroup(
        options = options,
        selectedOption = formState.selectedOption ?: options[0],
        onSelectedChange = {
            formState.selectedOption = it
        }
    )
}

Alongside the above properties, we can also apply some stylistic properties to this radio group constructor. Using the textStyle property we can pass an instance of a TextStyle, allowing us to style the text labels assigned to our radio group items:

RadioGroup(
    textStyle = TextStyle(fontWeight = FontWeight.Bold)
)

Using the radioColor property of the the Radio Group we can override the use of the secondary color from our application theme:

RadioGroup(
    …,
    radioColor = Color.Red
)

For the first Radio group function, that covers everything that we can achieve using the provided properties. The second Radio Group function offers us greater control over the contents of our Radio Group, if required. The second Radio Group composable function is much more minimal:

@Composable
fun RadioGroup(content: @Composable RadioGroupScope.() -> Unit) {
    val scope = remember { RadioGroupScope() }
    scope.content()
}

We can see here the content property that is required by the function, constrained by this RadioGroupScope – meaning that the provided children must adhere to this scope. If we dive into this scope with the RadioGroup source code, we can find the following:

@Stable
class RadioGroupScope internal constructor() {

    @Composable
    fun RadioGroupItem(
        selected: Boolean,
        onSelect: () -> Unit,
        content: @Composable() () -> Unit
    ) { … }

    @Composable
    fun RadioGroupTextItem(
        selected: Boolean,
        onSelect: () -> Unit,
        text: String,
        radioColor: Color = MaterialTheme.colors.secondary,
        textStyle: TextStyle? = null
    ) { … }

}

Here, the RadioGroupScope contains two components which can be used inside of our Radio Group – the RadioGroupItem and the RadioGroupTextItem. These don’t look too different at a first glance but there is on key difference between them. RadioGroupTextItem is used for items where you wish to just provide your own text value to be displayed as the label, whereas RadioGroupItem is used in cases where you want to provide the composable to be used for the whole radio item. Let’s look at both of these in a little more details, starting with the RadioGroupTextItem.

The requirements for building a RadioGroupTextItem are not too different from other properties that we’ve seen in this section so far, we can provide:

  • selected – whether or not the radio item is currently selected. This is required
  • onSelect – a lambda used to receive callback events for when the selection changes
  • text – the text to be used for the label of the radio item
  • radioColor – the color to be used for the radio items. If not provided, then the secondary color from the application theme will be applied
  • textStyle – the style to be applied to the text labels for the radio items

If we provide the minimum requirements for a RadioGroupTextItem, we’ll have something that might look a little like so:

@Composable
fun SimpleRadioGroupTextItemComponent(formState: RadioState) {
    val options = listOf("one", "two", "three")
    RadioGroup {
        RadioGroupTextItem(
            selected = …, 
            onSelect = {
                …
            }, 
            text = options[0]
        )
        RadioGroupTextItem(
            selected = …, 
            onSelect = {
                …
            }, 
            text = options[1]
        )
    }
}

Again we are using a list of strings to define the options for our radio items, however this time we need to manually access the list values when it comes to handling the selected state of each item. For example, let’s take a look at the first RadioGroupTextItem from our example above. We begin by manually assigning the text to the first item in the list:

RadioGroupTextItem(
    …,
    text = options[0]
)

Next we use our RadioState reference to check if the selected option is equal to the option at position 0 in our list. The result of this is then set to the selected property for our RadioGroupTextItem:

RadioGroupTextItem(
    selected = options[0] == formState.selectedOption, 
    …, 
    text = options[0]
)

Finally we need to handle when the selection of our item changes. For this we’ll utilise the onSelect lambda and set the value in our RadioState to reflect the corresponding option. 

RadioGroupTextItem(
    selected = options[0] == formState.selectedOption, 
    onSelect = {
        formState.selectedOption = options[0]
    }, 
    text = options[0]
)

Alongside these properties, we can provide a radioColor and or textStyle value – these behave the same way as previously seen in this section. Form this we can see that the RadioGroupTextItem provides us with a simple way of constructing an item to be displayed within a radio group. In cases where we may need more flexibility we can make use of the RadioGroupItem component. For this component there are three properties that we can pass into the function. Here, the selected and onSelect properties work exactly the same as seen for the RadioGroupTextItem. 

@Composable
fun SimpleRadioGroupTextItemComponent(formState: RadioState) {
    val options = listOf("one", "two", "three")
    RadioGroup {
        …
        RadioGroupItem(
            selected = options[2] == formState.selectedOption, 
            onSelect = {
                formState.selectedOption = options[2]
            }
        ) {
            …
        }
    }
}

The third property however, is a little different – content is used to pass a composable to be used for the content of the radio item. Using the RadioGroupItem allows you to essentially use a custom marker that is used to represent the selected state of the radio button, instead of what is usually provided out-of-the-box by the Radio Item components. Let’s take a look at an example of what this might look like:

@Composable
fun SimpleRadioGroupTextItemComponent(formState: RadioState) {
    val options = listOf("one", "two", "three")
    RadioGroup {
        …
        RadioGroupItem(
            selected = options[2] == formState.selectedOption, 
            onSelect = {
                formState.selectedOption = options[2]
            }
        ) {
            Row(modifier = Modifier.padding(10.dp)) {
                if (options[0] == formState.selectedOption) {
                    Image(asset = vectorResource(id =   
                        R.drawable.ic_android_black_24dp))
                } else {
                    Image(asset = vectorResource(id = 
                        R.drawable.ic_android_gray_24dp))
                }
                Text(
                    text = options[0],
                    modifier = Modifier.padding(start = 18.dp)
                )
            }
        }
    }
}

Here we provide a child component to our RadioGroup Item in the form of a Row component. Inside of this row we have two composables, an Image item and a Text item. Our text is used to display the option for the radio item, whereas we use the icon to depict the selected state of our radio button. Here we use the same images in two different colours, setting the image based on whether or not the option is the currently selected item. When presented as a group of these items, we would be given a result like so:


In this post we’ve taken a quick dive into the Radio Group component from Jetpack Compose. Stay tuned for the next post and in the meantime, subscribe to updates on my Jetpack Compose book 🙂

About the author

hitherejoe

Add Comment

By hitherejoe