ExpandableListView under 2 mins using jetpack compose

Bawender YandraBawender Yandra
4 min read

Recently while working on a requirement I stumbled upon a task that needed the ExpandableListView functionality from the old XML days. This is when I realized that there isn't really a composable in the Jetpack compose library that provides this solution. But looking at all the things that are available in the Compose library, I figured it shouldn't be hard to create our own custom composable that expands and hides its children.

In this article, we will touch upon the following points:

  • Creating custom composable functions with child composables.
  • Stateful composable and state handling inside them.
  • Adding simple visibility animation to a composable in Jetpack Compose.

So let's get started with it.

Components with children

Well, this should be an easy task. If we look at all the other composables that the Jetpack Compose library has then we can clearly see a pattern that they use. Most of the composables like Column, and Row, have a content parameter as their last parameter which itself is a composable function.

They are basically called Slots, where some other composable can be inserted inside a hosting composable. So let's create an ExpandableContainer composable.

@Composable
fun ExpandableContainer(
    title: String, 
    content: @Composable ColumnScope.() -> Unit,
) {

    Column(verticalArrangement = Arrangement.Center) {
        Text(text = title, style = heading2b)
        content()
    }

}

As you can see we have added the content parameter to this Composable function which is also a Composable function. I have also added a title parameter which will act as the heading for this section. This implementation then allows us to pass a child content that adds a title text to it.

The Slots are an important aspect of Jetpack Compose since they enforce the single responsibility principle. For instance, our ExpandableContainer should not care about what the content actually is. Its main job is to display a title and show or hide the content based on its state.

Note:

For the purpose of focussing more on understanding the functionality, we are omitting the UI beautification for the time being.

State Handling for Content Visibility

We wouldn't want the content to be visible at all times, we should only show the content when the user taps on it. We can also optionally take an initial state parameter to decide if the starting state is expanded or collapsed for the composable.

To implement this we need to introduce a state for this composable which should be an internal state and shouldn't be accessible/controlled from outside (If need be you could have it controlled, but I feel like its an overkill since this component should itself handle its content visibility state).



enum class ExpandableState { VISIBLE, HIDDEN }

@Composable
fun ExpandableContainer(
    title: String, 
    defaultState: ExpandableState = ExpandableState.HIDDEN,
    content: @Composable ColumnScope.() -> Unit,
) {

    //State
    var isContentVisible by rememberSavable { mutableStateOf(defaultState) }

   Column(
        modifier = Modifier
            .fillMaxWidth()
            .clickable {
                 isContentVisible =
                  if (isContentVisible == ExpandableState.VISIBLE) ExpandableState.HIDDEN else ExpandableState.VISIBLE 
            },
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = title, style = heading2b)

       AnimatedVisibility(visible = isContentVisible == ExpandableState.VISIBLE) {
            Column {
                content()
            }
        }
    }

}

Let's break down some of the things that are happening in the code written above.

  1. isContentVisible is the state that controls whether the content is visible or not. When the user presses the column then we toggle the visibility state of the composable. Notice I am using rememberSavable here, you can use remember as well in this case but rememberSavable survives view recycling in a LazyColumn.

  2. AnimatedVisibility is a composable that can toggle the visibility of its child component. It animates the appearance and disappearance of its content. If You look at the internal implementation of AnimatedVisibility, you will notice that the default animation for the content is fadeIn()/fadeOut() + expandVertically()/shrinkVertically(). This is perfect for our use case since we are making an expanding container. Also, notice I created another Column inside the AnimatedVisibility to wrap the content. This is done because by default AnimatedVisibility doesn't have any Layout of its own. The content will be rendered on top of one another, just like a Box.

  3. Lastly we have an enum ExpandableState which is a simple representation of the visibility state of the container.

With this, we now have an Expanding/Collapsing container that can be used in LazyLists or simple Column composable. Here is an implementation for using the above composable.


@Composable
fun MainActivityView() {

     LazyColumn(Modifier.fillMaxSize()) {

            item {
                 ExpandableContainer(
                    title = "Network type",
                ) {
                   //Some view that should be shown when the item is clicked.
                }
                Divider()
            }
     }
}

Conclusions


Even though there might not be an exact mapping of the XML based components in Jetpack Compose, we can still very easily make use of the Jetpack Compose lib to create our own custom composables. The Slots API demonstrated above is a very powerful way of reusing functionality and should be used when creating global composable functions.

Also the AnimatedVisibility composable is very easy to use and comes with inbuilt animations.

If you think this article has helped you understand Jetpack Compose better, consider sharing this article with your friends.

0
Subscribe to my newsletter

Read articles from Bawender Yandra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bawender Yandra
Bawender Yandra

Experienced professional with over 8 years of development in the mobile and web domain. I like writing and exploring different techniques of modern application development.