Simplifying Multi-Selection in Jetpack Compose

Meet Patadia
5 min readJun 14, 2024

--

While practicing Compose designing by cloning the UI of Spotify, I stumbled upon a challenge: implementing multi-selection for artist and podcast selections. After some research and experimentation, I discovered an incredibly effective and straightforward method to enable multi-selection in our layouts. This approach works seamlessly with both Lazy Column and Grid Layout, making it the simplest way to enhance your Jetpack Compose applications with multi-selection capabilities.

In this blog, I’ll walk you through the process step-by-step, ensuring you can effortlessly integrate this feature into your own projects. Whether you’re a beginner or an experienced developer, you’ll find this guide easy to follow and highly practical. Let’s dive in and elevate your Compose UI to the next level!

Game Plan Overview

We’re going to keep the flow simple for this, nothing complex.

  1. Create Layout: Set up the UI structure.
  2. Pass Data: Populate the layout with artist information.
  3. Add Multi-Selection Logic: Enable multi-selection functionality for the artist items.

Practical Implementation

Starting with the layout, create LazyVerticalGrid and ArtistView for a single artist layout.

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.codebyzebru.spotifycompose.models.Data

@Composable
fun PickArtist() {
LazyVerticalGrid(
state = rememberLazyGridState(),
columns = GridCells.Fixed(3),
verticalArrangement = Arrangement.spacedBy(3.dp),
horizontalArrangement = Arrangement.spacedBy(3.dp),
modifier = Modifier.fillMaxWidth()
) {
items(Data.artistsList) { artist ->
ArtistView(artist)
}
}
}

I assume it’s easy to understand what I have done in the above code, so I won’t delve too deeply into explaining this. Instead, I’ll provide a brief overview of the code:

  • LazyVerticalGrid is configured with a fixed number of columns (3), and spacing between items both vertically and horizontally.
  • It iterates over the list of artists from Data.artistsList.
  • For each artist, it calls the ArtistView composable to display the artist item.

Before we move ahead, let’s take a look at the Artist.kt data class:

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable

@Stable
data class Artist(
val img: String,
val name: String,
var isSelected: MutableState<Boolean>
) {
fun toggleSelection() {
isSelected.value = !isSelected.value
}
}
  • @Stable Annotation [optional]: Indicates that instances of this class have stable and consistent data over time, suitable for optimization in Compose.
  • We have our data class’s properties such as img for image URL, name represents name of artist, and isSelected MutableState of Boolean representing whether the artist is selected or not.
  • fun toggleSelection() to toggle the selection state of the artist.

Now, let’s see what ArtistView has got:

import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.codebyzebru.spotifycompose.models.Artist
import com.codebyzebru.spotifycompose.ui.theme.PrimaryGreen

@Composable
fun ArtistView(artists: Artist) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(horizontal = 5.dp, vertical = 15.dp)
.clickable(
enabled = true,
onClickLabel = "",
role = null,
onClick = {
artists.toggleSelection()
}
),
) {
AsyncImage(
model = artists.img,
contentDescription = "",
modifier = Modifier
.aspectRatio(1f)
.clip(CircleShape)
.size(100.dp)
.border(
width = if (artists.isSelected.value) 3.dp else 0.dp,
color = if (artists.isSelected.value) PrimaryGreen else Color.Transparent,
shape = CircleShape
),
contentScale = ContentScale.Crop,
)

Text(
text = artists.name,
color = if (artists.isSelected.value) PrimaryGreen else Color.White,
fontSize = 13.sp,
modifier = Modifier.padding(top = 5.dp),
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
lineHeight = 16.sp
)
}
}

As you can see, I’ve made the column clickable and toggled the selection state. And that’s it! Now we have multi-selection enabled in our layout.

BUT WHAT IF YOU NEED THE DATA OF THE SELECTED ITEMS?

It’s obvious that we enabled multi-selection in our layout for a reason, so let’s achieve that.

To do this, we’ll need a list to keep track of the selected items’ IDs or unique keys. This list will help us handle item clicks for selection and deselection.

// other imports
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable

@Composable
fun PickArtist(
modifier: Modifier = Modifier,
) {

val selectedIndexList = rememberSaveable {
mutableStateOf(emptySet<Int>())
}

// ...
}
  • A state variable selectedIndexList to keep track of selected items.

Now we’ll need ‘onClick’ callback. For the same we’ll need to make some changes in our ArtistView.

@Composable
fun ArtistView(
artists: Artist,
position: Int,
onItemClick: (Int) -> Unit
)

Include position and onItemClick callback parameters. You can use your own unique identifier or value that you prefer to add to the list.

(Note: position is optional If you’re already getting ID or Unique key from the data class, you don’t need to include `position` in your Compose code.)

Now, add a callback to your Column’s onClick, and pass the artist’s ID or unique key in the callback. This way, we can retrieve that value in the LazyVerticalGrid.

onClick = {
artists.toggleSelection()
onItemClick(position)
}

Done for ArtistView. Now let’s complete the LazyVerticalGrid side.

Here, I’m using itemsIndexed{}. This allows me to get the index of each item along with the item itself from the lambda function. I'm taking the item's position as a unique key.

import androidx.compose.foundation.lazy.grid.itemsIndexed

// ...

itemsIndexed(Data.artistsList) { index, artist ->

}

Now, let’s integrate the ArtistView composable inside this block instead of using items{}.

itemsIndexed(Data.artistsList) { index, artist ->
ArtistView(
artists = artist,
position = index,
onItemClick = { position ->

}
)
}

As you can see, we are receiving the position in our onItemClick lambda function. Here, we’ll add this position/ID/Unique Key to our selectedIndexList.

onItemClick = { position ->
selectedIndexList.value = if (item.isSelected.value) {
selectedIndexList.value.plus(position)
} else {
selectedIndexList.value.minus(position)
}
}
  • If the item is selected (item.isSelected.value), we add its position to the selectedIndexList using the plus() function. If the item is not selected, we remove its position from the selectedIndexList using the minus() function. Simple? NO ROCKET SCIENCE.

And we are done! Congratulations on enabling multi-selection in your layout. Now take a break and have a KitKat ;)

Worth you attention:
BEST way to enable search in Lazy Layouts in Jetpack Compose

I hope this article has provided valuable insights and assistance for your Android journey. Your support means a lot to me, so if you found this content helpful, please start following me and don’t hesitate to show some love with a hearty round of applause!👏

Your feedback fuels my passion for creating quality content. For any Android queries or just to connect, reach out on LinkedIn and Twitter.

Thanks for reading — looking forward to staying in touch!

Happy Coding!!

--

--

Meet Patadia

Software Developer - Android, Java, Kotlin, MVVM, Jetpack Compose