1. Before you begin
In this codelab, you learn how to make a scrollable list in your app using Jetpack Compose.
You will be working with the Affirmations app, which displays a list of affirmations paired with beautiful images to bring positivity to your day!
The data is already there, all you need to do is take that data and display it in the UI.
Prerequisites
- Familiarity with Lists in Kotlin
- Experience building layouts with Jetpack Compose
- Experience running apps on a device or emulator
What you'll learn
- How to create a material design card using Jetpack Compose
- How to create a scrollable list using Jetpack Compose
What you'll build
- You will take an existing application and add a scrollable list to the UI
The finished product will look like this:
What you'll need
- A computer with internet access, a web browser, and Android Studio
- Access to GitHub
Download the starter code
In Android Studio, open the basic-android-kotlin-compose-training-affirmations
folder.
- Navigate to the provided GitHub repository page for the project.
- Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.
- On the GitHub page for the project, click the Code button, which brings up a popup.
- In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
- Locate the file on your computer (likely in the Downloads folder).
- Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.
Open the project in Android Studio
- Start Android Studio.
- In the Welcome to Android Studio window, click Open.
Note: If Android Studio is already open, instead, select the File > Open menu option.
- In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
- Double-click on that project folder.
- Wait for Android Studio to open the project.
- Click the Run button to build and run the app. Make sure it builds as expected.
2. Create a list item data class
Create a data class for an Affirmation
In Android apps, lists are made up of list items. For single pieces of data, this could be something simple like a string or an integer. For list items that have multiple pieces of data, like an image and text, you will need a class that contains all of these properties. Data classes are a type of class that only contain properties, they can provide some utility methods to work with those properties.
- Create a new package under com.example.affirmations.
Name the new package model. The model package will contain the data model that will be represented by a data class. That data class will be comprised of properties that represent the information relevant to what will be an "Affirmation," which will consist of a string resource and an image resource. Packages are directories that contain classes and even other directories.
- Create a new class in the com.example.affirmations.model package.
Name the new class Affirmation and make it a Data Class.
- Each
Affirmation
consists of one image and one string. Create twoval
properties in theAffirmation
data class. One should be calledstringResourceId
and the otherimageResourceId
. They should both be integers.
Affirmation.kt
data class Affirmation(
val stringResourceId: Int,
val imageResourceId: Int
)
- Tag the
stringResourceId
property with the@StringRes
annotation and tagimageResourceId
with@DrawableRes
. ThestringResourceId
represents an ID for the affirmation text stored in a string resource. TheimageResourceId
represents an ID for the affirmation image stored in a drawable resource.
Affirmation.kt
data class Affirmation(
@StringRes val stringResourceId: Int,
@DrawableRes val imageResourceId: Int
)
- Now open the
Datasource.kt
file in the com.example.affirmations.data package and uncomment the contents of theDatasource
class.
Datasource.kt
class Datasource() {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1, R.drawable.image1),
Affirmation(R.string.affirmation2, R.drawable.image2),
Affirmation(R.string.affirmation3, R.drawable.image3),
Affirmation(R.string.affirmation4, R.drawable.image4),
Affirmation(R.string.affirmation5, R.drawable.image5),
Affirmation(R.string.affirmation6, R.drawable.image6),
Affirmation(R.string.affirmation7, R.drawable.image7),
Affirmation(R.string.affirmation8, R.drawable.image8),
Affirmation(R.string.affirmation9, R.drawable.image9),
Affirmation(R.string.affirmation10, R.drawable.image10))
}
}
3. Add a list to your app
Create a list item card
This app is meant to display a list of affirmations. The first step in configuring the UI to display a list is to create a list item. Each affirmation item consists of an image and a string. The data for each of these items comes with the starter code, and you will create the UI component to display such an item.
The item will be comprised of a Card
composable, containing an Image
and a Text
composable. In Compose, a Card
is a surface that displays content and actions in a single container. The Affirmation card will look like this:
The card shows an image with some text beneath it. This vertical layout can be achieved using a Column
composable wrapped in a Card
composable. You can give it a try on your own, or follow the steps below to achieve this.
- Open the MainActivity.kt file.
- Create a new method beneath the
AffirmationApp()
method, calledAffirmationCard()
, and annotate it with the@Composable
annotation.
MainActivity.kt
@Composable
fun AffirmationApp() {
val context = LocalContext.current
AffirmationsTheme {
}
}
@Composable
fun AffirmationCard() {
}
- Edit the method signature to take an
Affirmation
object as a parameter. TheAffirmation
object comes from themodel
package.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation) {
}
- Add a
modifier
parameter to the signature. Set a default value ofModifier
for the parameter.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
}
- Inside of the
AffirmationCard
method, call theCard
composable. Pass in the following parameters:modifier
andelevation
. Pass aModifier
object with thepadding
attribute set to8.dp
for themodifier
parameter. Pass a value of4.dp
for theelevation
. Theelevation
property is covered in more detail later on.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {Card(modifier = modifier.padding(8.dp), elevation = 4.dp) {
}
}
- Add a
Column
composable inside of theCard
composable. Items within aColumn
composable arrange themselves vertically in the UI. This allows you to place an image above the associated text. Conversely, aRow
composable arranges its contained items horizontally.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = modifier.padding(8.dp), elevation = 4.dp) {
Column {
}
}
}
- Add an
Image
composable inside of the lambda body of theColumn
composable. Recall that anImage
composable always requires a resource to display, and acontentDescription
. The resource should be apainterResource
passed to thepainter
parameter. ThepainterResource
method will load either vector drawables or rasterized asset formats like PNGs. Also, pass astringResource
for thecontentDescription
parameter.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = Modifier.padding(8.dp), elevation = 4.dp) {
Column {
Image(
painter = painterResource(affirmation.imageResourceId),
contentDescription = stringResource(affirmation.stringResourceId)
)
}
}
}
- In addition to the
painter
andcontentDescription
parameters, pass amodifier
and acontentScale
. AcontentScale
determines how the image should be scaled and displayed. TheModifier
object should have thefillMaxWidth
attribute set and a height of194.dp
. ThecontentScale
should beContentScale.Crop
.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = Modifier.padding(8.dp), elevation = 4.dp) {
Column {
Image(
painter = painterResource(affirmation.imageResourceId),
contentDescription = stringResource(affirmation.stringResourceId),
modifier = Modifier
.fillMaxWidth()
.height(194.dp),
contentScale = ContentScale.Crop
)
}
}
}
- Inside of the
Column
, create aText
composable after theImage
composable. Pass astringResource
of theaffirmation.stringResourceId
to thetext
parameter, pass aModifier
object with thepadding
attribute set to16.dp
, and set a text theme by passingMaterialTheme.typography.h6
to thestyle
parameter.
MainActivity.kt
@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
Card(modifier = Modifier.padding(8.dp), elevation = 4.dp) {
Column {
Image(
painter = painterResource(affirmation.imageResourceId),
contentDescription = stringResource(affirmation.stringResourceId),
modifier = Modifier
.fillMaxWidth()
.height(194.dp),
contentScale = ContentScale.Crop
)
Text(
text = stringResource(affirmation.stringResourceId),
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.h6
)
}
}
}
Preview the AffirmationCard composable
The card is the core of the UI for the Affirmations app, and you worked hard to create it! To check that the card looks correct, you can create a composable that can be previewed without launching the entire app.
- Create a private method called
AffirmationCardPreview()
. Annotate the method with@Preview
and@Composable
.
MainActivity.kt
@Preview
@Composable
private fun AffirmationCardPreview() {
}
- Inside of the method, call the
AffirmationCard
composable, and pass it a newAffirmation
object with theR.string.affirmation1
string resource and theR.drawable.image1
drawable resource passed into its constructor.
MainActivity.kt
@Preview
@Composable
private fun AffirmationCardPreview() {
AffirmationCard(Affirmation(R.string.affirmation1, R.drawable.image1))
}
- Open the Split tab and you will see a preview of the
AffirmationCard
. If necessary, click Build & Refresh in the Design pane to display the preview.
Create the list
The list item component is the building block of the list. Once the list item is created, you can leverage it to make the list component itself.
- Create a method called
AffirmationList()
, annotate it with the@Composable
annotation, and declare aList
ofAffirmation
objects as a parameter in the method signature.
MainActivity.kt
@Composable
private fun AffirmationList(affirmationList: List<Affirmation>) {
}
- Declare a
modifier
object as a parameter in the method signature with a default value ofModifier
.
MainActivity.kt
@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
}
- In Jetpack Compose, a scrollable list can be made using the
LazyColumn
composable. The difference between aLazyColumn
and aColumn
is that aColumn
should be used when you have a small number of items to display, as Compose loads them all at once. AColumn
can only hold a predefined, or fixed, number of composables. ALazyColumn
can add content on demand, which makes it good for long lists and particularly when the length of the list is unknown. ALazyColumn
also provides scrolling by default, without additional code. Declare aLazyColumn
composable inside of theAffirmationList()
method.
MainActivity.kt
@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn {
}
}
- In the lambda body of the
LazyColumn
, call theitems()
method, and pass in theaffirmationList
. Theitems()
method is how you add items to theLazyColumn
. This method is somewhat unique to this composable, and is otherwise not a common practice for most composables.
MainActivity.kt
@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn {
items(affirmationList){
}
}
}
- A call to the
items()
method requires a lambda function. In that function, specify a parameter ofaffirmation
that represents one affirmation item from theaffirmationList
.
MainActivity.kt
@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn {
items(affirmationList){ affirmation ->
}
}
}
- For each affirmation in the list, call the
AffirmationCard()
composable, and pass it theaffirmation
.
MainActivity.kt
@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
LazyColumn {
items(affirmationList){ affirmation ->
AffirmationCard(affirmation)
}
}
}
Display the list
- In the lambda, call the
AffirmationList
composable, and passDataSource().loadAffirmations()
to theaffirmationList
parameter.
MainActivity.kt
@Composable
fun AffirmationApp() {
AffirmationsTheme {
Scaffold(
content = {
AffirmationList(affirmationList = Datasource().loadAffirmations())
}
)
}
}
Run the Affirmations app on a device or emulator and see the finished product!
4. Get the solution code
If you want to see the solution code, view it on GitHub.
- Navigate to the provided GitHub repository page for the project.
- Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.
- On the GitHub page for the project, click the Code button, which brings up a popup.
- In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
- Locate the file on your computer (likely in the Downloads folder).
- Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.
Open the project in Android Studio
- Start Android Studio.
- In the Welcome to Android Studio window, click Open.
Note: If Android Studio is already open, instead, select the File > Open menu option.
- In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
- Double-click on that project folder.
- Wait for Android Studio to open the project.
- Click the Run button to build and run the app. Make sure it builds as expected.
5. Conclusion
You now know how to create cards, list items, and scrollable lists using Jetpack Compose! Keep in mind that these are just basic tools for creating a list. You can let your creativity roam and customize list items however you like!
Summary
- Use
Card
composables to create list items. - Modify the UI contained within a
Card
composable. - Create a scrollable list using the
LazyColumn
composable. - Build a list using custom list items.