

How to make Expandable List with Jetpack Compose
source link: https://johncodeos.com/how-to-make-expandable-list-with-jetpack-compose/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Last updated on: August 27, 2022
In this tutorial, I’ll show you how to make an expandable list using Jetpack Compose.
Each row contains two composable views: a header (HeaderView), and an expandable view (ExpandableView).
The user taps the HeaderView, and the ExpandableView expands or collapses according to its previous state.
When we tap an item, we store its index in a list and update the UI accordingly using Kotlin Coroutines Flow.
Creating the ViewModel
Let’s begin by creating the view model, where we’ll put the business logic.
We’ll create two methods—one for getting the demo data and another for storing or deleting the ids from the list—when we expand or collapse an item.
Right-click on the project name folder > New > Kotlin Class/File
We choose Class, and we name it ExpandableListViewModel
Next, we extend the ViewModel class
import androidx.lifecycle.ViewModel
class ExpandableListViewModel : ViewModel() {
// ...
}
Code language: Swift (swift)
After that, we create a new method called getData(), which gets the data. In this example, we have an array list of demo data stored using a model.
class ExpandableListViewModel : ViewModel() {
private val itemsList = MutableStateFlow(listOf<DataModel>())
val items: StateFlow<List<DataModel>> get() = itemsList
private fun getData() {
viewModelScope.launch {
withContext(Dispatchers.Default) {
itemsList.emit(Data.items)
}
}
}
}
Code language: Kotlin (kotlin)
data class DataModel(val question: String, val answer: String)
Code language: Kotlin (kotlin)
object Data {
var items = arrayListOf(
DataModel(
"Item 0",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
),
DataModel(
"Item 1",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam"
),
DataModel(
"Item 2",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut"
),
DataModel(
"Item 3",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
),
DataModel(
"Item 4",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
),
DataModel(
"Item 5",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
),
// ...
)
}
Code language: Kotlin (kotlin)
We call this method in the constructor, so whenever the screen appears, we load the data.
class ExpandableListViewModel : ViewModel() {
// ...
init {
getData()
}
private fun getData() {
// ...
}
}
Code language: Kotlin (kotlin)
Lastly, we create another method for when we tap the view. This method will check if the id exists in the itemIdsList; if not, it will be added.
class ExpandableListViewModel : ViewModel() {
// ...
private val itemIdsList = MutableStateFlow(listOf<Int>())
val itemIds: StateFlow<List<Int>> get() = itemIdsList
init {
getData()
}
private fun getData() {
// ...
}
fun onItemClicked(itemId: Int) {
itemIdsList.value = itemIdsList.value.toMutableList().also { list ->
if (list.contains(itemId)) {
list.remove(itemId)
} else {
list.add(itemId)
}
}
}
}
Code language: Kotlin (kotlin)
So, in the end, our ViewModel
will look like this:
class ExpandableListViewModel : ViewModel() {
private val itemsList = MutableStateFlow(listOf<DataModel>())
val items: StateFlow<List<DataModel>> get() = itemsList
private val itemIdsList = MutableStateFlow(listOf<Int>())
val itemIds: StateFlow<List<Int>> get() = itemIdsList
init {
getData()
}
private fun getData() {
viewModelScope.launch {
withContext(Dispatchers.Default) {
itemsList.emit(Data.items)
}
}
}
fun onItemClicked(itemId: Int) {
itemIdsList.value = itemIdsList.value.toMutableList().also { list ->
if (list.contains(itemId)) {
list.remove(itemId)
} else {
list.add(itemId)
}
}
}
}
Code language: Kotlin (kotlin)
Making the Expandable List UI
Let’s begin by making the view that we tap to expand or collapse the expandable view.
It’s a clickable Box with Text in it.
// ...
@Composable
fun HeaderView(questionText: String, onClickItem: () -> Unit) {
Box(
modifier = Modifier
.background(colorResource(R.color.colorPrimary))
.clickable(
indication = null, // Removes the ripple effect on tap
interactionSource = remember { MutableInteractionSource() }, // Removes the ripple effect on tap
onClick = onClickItem
)
.padding(8.dp)
) {
Text(
text = questionText,
fontSize = 17.sp,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
)
}
}
@Preview(showBackground = true)
@Composable
fun HeaderViewPreview() {
HeaderView("Question") {}
}
Code language: Kotlin (kotlin)
Next, we create the expandable view.
We create an EnterTransition that expands the view with 300 ms duration, and we apply a fade-in effect to make the transition look cooler.
Then we do an ExitTransition for the collapsing animation.
Lastly, we use the AnimatedVisibility to add the transitions to the view and check whether we should hide it or not with the isExpanded parameter.
// ...
@Composable
fun ExpandableView(answerText: String, isExpanded: Boolean) {
// Opening Animation
val expandTransition = remember {
expandVertically(
expandFrom = Alignment.Top,
animationSpec = tween(300)
) + fadeIn(
animationSpec = tween(300)
)
}
// Closing Animation
val collapseTransition = remember {
shrinkVertically(
shrinkTowards = Alignment.Top,
animationSpec = tween(300)
) + fadeOut(
animationSpec = tween(300)
)
}
AnimatedVisibility(
visible = isExpanded,
enter = expandTransition,
exit = collapseTransition
) {
Box(
modifier = Modifier
.background(colorResource(R.color.colorPrimaryDark))
.padding(15.dp)
) {
Text(
text = answerText,
fontSize = 16.sp,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
)
}
}
}
@Preview(showBackground = true)
@Composable
fun ExpandableViewPreview() {
ExpandableView("Answer", true)
}
Code language: Kotlin (kotlin)
Now, let’s put them together using a Column
@Composable
fun ExpandableContainerView(itemModel: DataModel, onClickItem: () -> Unit, expanded: Boolean) {
Box(
modifier = Modifier
.background(colorResource(R.color.colorPrimaryDark))
) {
Column {
HeaderView(questionText = itemModel.question, onClickItem = onClickItem)
ExpandableView(answerText = itemModel.answer, isExpanded = expanded)
}
}
}
@Preview(showBackground = true)
@Composable
fun ExpandableContainerViewPreview() {
ExpandableContainerView(
itemModel = DataModel("Question", "Answer"),
onClickItem = {},
expanded = true
)
}
Code language: Kotlin (kotlin)
Next, we initialize the view model, which we pass to the composable function MainScreen
class MainActivity : ComponentActivity() {
private val viewModel by viewModels<ExpandableListViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen(viewModel)
}
}
}
@Composable
fun MainScreen(viewModel: ExpandableListViewModel) {
Scaffold(
topBar = { TopBar() }
) { padding -> // We need to pass scaffold's inner padding to the content
// ...
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
val viewModel = ExpandableListViewModel()
MainScreen(viewModel = viewModel)
}
@Composable
fun TopBar() {
TopAppBar(
title = { Text(text = stringResource(R.string.app_name), fontSize = 18.sp) },
backgroundColor = colorResource(id = R.color.colorPrimary),
contentColor = Color.White
)
}
@Preview(showBackground = false)
@Composable
fun TopBarPreview() {
TopBar()
}
Code language: Kotlin (kotlin)
Finally, we use LazyColumn to create the list, and we loop the list using itemsIndexed to pass the index as an item id.
Finally, we use the LazyColumn composable function for the list, and we loop through the list using the itemsIndexed because we want to pass the index as an item id.
@Composable
fun MainScreen(viewModel: ExpandableListViewModel) {
val itemIds by viewModel.itemIds.collectAsState()
Scaffold(
topBar = { TopBar() }
) { padding -> // We need to pass scaffold's inner padding to the content
LazyColumn(modifier = Modifier.padding(padding)) {
itemsIndexed(viewModel.items.value) { index, item ->
ExpandableContainerView(
itemModel = item,
onClickItem = { viewModel.onItemClicked(index) },
expanded = itemIds.contains(index)
)
}
}
}
}
Code language: Kotlin (kotlin)
Note: If you run the app on an emulator or device, you might notice some lagging when you scroll. This is normal. When you run a debug version of a compose app, the performance might be slower. This is because Compose translates bytecode in runtime using JIT. Make sure you’re also using the R8 compiler in the release build. That’s extremely important to improve general performance.
You can find the final project here
If you have any questions, please feel free to leave a comment below
Recommend
-
55
Last week the U.S. Patent & Trademark Office published a patent application from Samsung that reveals a twist on their past scrollable device patents that we covered here and here. One of Samsung's latest patents centers on a traditional sma...
-
14
Responses You have 2 free member-only stories left this month.
-
4
How To Make Expandable UITableViewCells For Dynamic Text Height With Auto Layout UITableViewCell objects are the core elements for table views and are used to draw the table view visible rows. Alth...
-
19
Building an Expandable List Using UICollectionView: Part 2 In last week’s article, we learned how to leverage the NSDiffableDataSourceSecti...
-
29
Building an Expandable List Using UICollectionView: Part 1 Expandable list or some might call it expandable table view, is a very common UI design that is used in a large variety of apps. Despite the fact that it is so popular, UIKit...
-
26
Replicate the Expandable Date Picker Using UICollectionView List The expandable date picker is a very common UI component in the Apple design system. You can find it in the iOS Reminder and Calendar App. Even though it does not come...
-
4
-
8
make expandable explanations ...and here's all the Nutshells made for this page:
-
10
SwiftUI Build Dynamic And Expandable SwiftUI List With DisclosureGroup In the
-
9
How to make expandable text with a button in Jetpack ComposeApril 28, 2023 5 minIntroduction
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK