In the dynamic world of Android app development, Jetpack Compose has emerged a powerful toolkit, revolutionising the way we create user interfaces. It comes with all sorts of ready made Composables that can present list data beautifully.
In this article, we’ll delve into the versatility of Jetpack Compose by exploring the seamless transition between two fundamental Composables: LazyVerticalGrid
and LazyColumn
. Join me as we see how easy it is to switch between the two, uncovering tips and tricks along the way to enhance your Compose development experience.
The setup
Step 1: A simple Card
For this to make sense, I’ll need to work incrementally so let’s start by creating a card that will later be displayed as either a grid or a list.
@Composable
fun AlbumCard(album: Album, modifier: Modifier = Modifier) {
Card(
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface),
) {
Row {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "random image",
contentScale = ContentScale.Fit,
modifier = Modifier.padding(8.dp),
)
Column {
Text(
text = album.title,
modifier = Modifier.padding(top = 8.dp, end = 16.dp),
style = MaterialTheme.typography.titleMedium
)
Text(
text = album.artist,
modifier = Modifier.padding(top = 4.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
data class Album(
val title: String,
val artist: String,
)
Step 2: Adding the card to the screen
Now we need to actually display the card on our screen. I’ll create another composable called TopAlbumsScreen
which takes our card then uses a LazyColumn
to display our list as a vertically scrollable list of AlbumCards
. This screen can then be added to the MainActivity’s setContent
method.
@Composable
fun TopAlbumsScreen() {
Scaffold(
topBar = {
MainAppBar()
},
content = { padding ->
Surface(
modifier = Modifier.padding(padding),
color = MaterialTheme.colorScheme.background,
) {
val albums = Datasource().loadEntries()
LazyColumn {
items(albums) { album ->
AlbumCard(album)
}
}
}
}
)
}
Your Card should look something like this now:
The implementation
Step 3: Creating the Grid Card
Now, we need to create a new Card which will display the data as a grid rather than a list. It’s pretty similar to the existing AlbumCard
except instead of a Row
as the root element, we have a Column
. To keep things clear, I’ll call this new card AlbumCardGrid
and rename the existing AlbumCard
to something more specific like AlbumCardList
.
@Composable
fun AlbumCardGrid(album: Album, modifier: Modifier = Modifier) {
Card(
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface),
) {
Column(modifier = Modifier.padding(8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "random image",
contentScale = ContentScale.Fit,
modifier = Modifier.padding(8.dp),
)
Text(
text = album.title,
modifier = Modifier.padding(top = 8.dp),
style = MaterialTheme.typography.titleSmall
)
Text(
text = album.artist,
modifier = Modifier.padding(top = 4.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
Step 4: Creating the new AlbumCard
This is where things start to get a bit more interesting. We should now have two Cards; AlbumCardGrid
and AlbumCardList
. Since AlbumCard
no longer exists, I’ll create a new AlbumCard
which will choose between the two cards depending on some value. In this example, the value is a boolean named isList
.
@Composable
fun AlbumCard(
albums: List<Album>,
isList: Boolean,
) {
AnimatedVisibility(
visible = !isList,
enter = fadeIn(),
exit = fadeOut(),
) {
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(items = albums) { album ->
AlbumCardGrid(album = album)
}
}
}
AnimatedVisibility(
visible = isList,
enter = fadeIn(),
exit = fadeOut(),
) {
LazyColumn {
items(albums) { album ->
AlbumCardList(album)
}
}
}
}
You’ll see that we used theAnimatedVisibility
composable which animates the appearance and disappearance of its content and allows you to hide or show content easily.
Step 5: Managing State
Now that the AlbumCard has changed, our TopAlbumScreen composable will need to look a little bit different too. The main change is that there is now an isList
variable created, which is a rememberSaveable
function and is true by default. The rememberSaveable
function is nifty because it helps you retain state across recomposition and configuration changes.
@Composable
fun TopAlbumsScreen() {
var isList by rememberSaveable { mutableStateOf(true) }
Scaffold(
topBar = {
MainAppBar(
isList = isList,
onLayoutChangeRequested = { isList = !isList })
},
content = { padding ->
Surface(
modifier = Modifier.padding(padding),
color = MaterialTheme.colorScheme.background,
) {
val albums = Datasource().loadEntries()
AlbumCard(
albums = albums,
isList = isList
)
}
}
)
}
If you’re paying close attention, you’ll notice that the MainAppBar
has also changed, this is because the option to switch between grid and list view is done in the TopAppBar
. Have a look at the TopAppBar
implementation below, paying close attention to the actions
parameter.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainAppBar(isList: Boolean, onLayoutChangeRequested: () -> Unit) {
TopAppBar(
title = { Text("Knockoff Spotify") },
actions = {
IconButton(onClick = {
onLayoutChangeRequested()
}) {
if (!isList) {
Icon(
imageVector = Icons.Filled.List,
contentDescription = "list view",
)
}
else {
Icon(
imageVector = Icons.Filled.GridView,
contentDescription = "grid view",
)
}
}
},
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.primaryContainer),
)
}
You can see that in order to know which icon to show, we use the isList
state parameter which has been hoisted from the TopAlbumScreen. When the icon is clicked, we also call the onLayoutChangeRequested()
function which will change the value of isList
from true to false and vice versa.
The final product will look something like this, albeit without all the cool pictures.
If you’d like to see the full project on github, you can check it out here. Thanks for sticking around!