Add a scrollable list

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:

f6f09800b74f4700.png

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.

  1. Navigate to the provided GitHub repository page for the project.
  2. Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.

1e4c0d2c081a8fd2.png

  1. On the GitHub page for the project, click the Code button, which brings up a popup.

1debcf330fd04c7b.png

  1. In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
  2. Locate the file on your computer (likely in the Downloads folder).
  3. Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.

Open the project in Android Studio

  1. Start Android Studio.
  2. In the Welcome to Android Studio window, click Open.

d8e9dbdeafe9038a.png

Note: If Android Studio is already open, instead, select the File > Open menu option.

8d1fda7396afe8e5.png

  1. In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
  2. Double-click on that project folder.
  3. Wait for Android Studio to open the project.
  4. Click the Run button 8de56cba7583251f.png 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.

  1. Create a new package under com.example.affirmations. 4a51cb670bbec405.png

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.

9ea1f8880ca90ea0.png

  1. Create a new class in the com.example.affirmations.model package. a9515a8b47715a22.png

Name the new class Affirmation and make it a Data Class.

b36be7f428fd1672.png

  1. Each Affirmation consists of one image and one string. Create two val properties in the Affirmation data class. One should be called stringResourceId and the other imageResourceId. They should both be integers.

Affirmation.kt

data class Affirmation(
   val stringResourceId: Int,
   val imageResourceId: Int
)
  1. Tag the stringResourceId property with the @StringRes annotation and tag imageResourceId with @DrawableRes. The stringResourceId represents an ID for the affirmation text stored in a string resource. The imageResourceId 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
)
  1. Now open the Datasource.kt file in the com.example.affirmations.data package and uncomment the contents of the Datasource 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:

95111184aed54fa3.png

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.

  1. Open the MainActivity.kt file.

1e348baaf91552f4.png

  1. Create a new method beneath the AffirmationApp() method, called AffirmationCard(), and annotate it with the @Composable annotation.

MainActivity.kt

@Composable
fun AffirmationApp() {
   val context = LocalContext.current
   AffirmationsTheme {
    
   }
}

@Composable
fun AffirmationCard() {
  
}
  1. Edit the method signature to take an Affirmation object as a parameter. The Affirmation object comes from the model package.

MainActivity.kt

@Composable
fun AffirmationCard(affirmation: Affirmation) {
  
}
  1. Add a modifier parameter to the signature. Set a default value of Modifier for the parameter.

MainActivity.kt

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
  
}
  1. Inside of the AffirmationCard method, call the Card composable. Pass in the following parameters: modifier and elevation. Pass a Modifier object with the padding attribute set to 8.dp for the modifier parameter. Pass a value of 4.dp for the elevation. The elevation 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) {
  }
}
  1. Add a Column composable inside of the Card composable. Items within a Column composable arrange themselves vertically in the UI. This allows you to place an image above the associated text. Conversely, a Row 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 {
    }
  }
}
  1. Add an Image composable inside of the lambda body of the Column composable. Recall that an Image composable always requires a resource to display, and a contentDescription. The resource should be a painterResource passed to the painter parameter. The painterResource method will load either vector drawables or rasterized asset formats like PNGs. Also, pass a stringResource for the contentDescription 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)
      )
    }
  }
}
  1. In addition to the painter and contentDescription parameters, pass a modifier and a contentScale. A contentScale determines how the image should be scaled and displayed. The Modifier object should have the fillMaxWidth attribute set and a height of 194.dp. The contentScale should be ContentScale.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
      )
    }
  }
}
  1. Inside of the Column, create a Text composable after the Image composable. Pass a stringResource of the affirmation.stringResourceId to the text parameter, pass a Modifier object with the padding attribute set to 16.dp, and set a text theme by passing MaterialTheme.typography.h6 to the style 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.

  1. Create a private method called AffirmationCardPreview(). Annotate the method with @Preview and @Composable.

MainActivity.kt

@Preview 
@Composable
private fun AffirmationCardPreview() {
}
  1. Inside of the method, call the AffirmationCard composable, and pass it a new Affirmation object with the R.string.affirmation1 string resource and the R.drawable.image1 drawable resource passed into its constructor.

MainActivity.kt

@Preview 
@Composable
private fun AffirmationCardPreview() {
  AffirmationCard(Affirmation(R.string.affirmation1, R.drawable.image1))
}
  1. 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. 84904da4a33413ce.png

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.

  1. Create a method called AffirmationList(), annotate it with the @Composable annotation, and declare a List of Affirmation objects as a parameter in the method signature.

MainActivity.kt

@Composable
private fun AffirmationList(affirmationList: List<Affirmation>) {
}
  1. Declare a modifier object as a parameter in the method signature with a default value of Modifier.

MainActivity.kt

@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
}
  1. In Jetpack Compose, a scrollable list can be made using the LazyColumn composable. The difference between a LazyColumn and a Column is that a Column should be used when you have a small number of items to display, as Compose loads them all at once. A Column can only hold a predefined, or fixed, number of composables. A LazyColumn can add content on demand, which makes it good for long lists and particularly when the length of the list is unknown. A LazyColumn also provides scrolling by default, without additional code. Declare a LazyColumn composable inside of the AffirmationList() method.

MainActivity.kt

@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
  LazyColumn {
  }
}
  1. In the lambda body of the LazyColumn, call the items() method, and pass in the affirmationList. The items() method is how you add items to the LazyColumn. 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){
    }
  }
}
  1. A call to the items() method requires a lambda function. In that function, specify a parameter of affirmation that represents one affirmation item from the affirmationList.

MainActivity.kt

@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
  LazyColumn {
    items(affirmationList){ affirmation ->
    }
  }
}
  1. For each affirmation in the list, call the AffirmationCard() composable, and pass it the affirmation.

MainActivity.kt

@Composable
private fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
  LazyColumn {
    items(affirmationList){ affirmation ->
      AffirmationCard(affirmation)
    }
  }
}

Display the list

  1. In the lambda, call the AffirmationList composable, and pass DataSource().loadAffirmations() to the affirmationList 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!

f6f09800b74f4700.png

4. Get the solution code

If you want to see the solution code, view it on GitHub.

  1. Navigate to the provided GitHub repository page for the project.
  2. Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.

1e4c0d2c081a8fd2.png

  1. On the GitHub page for the project, click the Code button, which brings up a popup.

1debcf330fd04c7b.png

  1. In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
  2. Locate the file on your computer (likely in the Downloads folder).
  3. Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.

Open the project in Android Studio

  1. Start Android Studio.
  2. In the Welcome to Android Studio window, click Open.

d8e9dbdeafe9038a.png

Note: If Android Studio is already open, instead, select the File > Open menu option.

8d1fda7396afe8e5.png

  1. In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
  2. Double-click on that project folder.
  3. Wait for Android Studio to open the project.
  4. Click the Run button 8de56cba7583251f.png 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.