Create a catalog browser

A media app that runs on a TV needs to allow users to browse its content offerings, make a selection, and start playing content. The content browsing experience for apps of this type should be simple and intuitive, and visually pleasing and engaging.

This section describes how to use the functions provided by Compose for TV to implement a user interface for browsing music or videos from your app's media catalog.

Figure 1. Typical catalog screen. Users are able to browse video catalog data.

A media catalog browser tends to consist of several sections, and each section has a list of media content. Examples of sections in a media catalog include: playlists, featured content, recommended categories

Create a composable function for catalog

Everything appearing on a display is implemented as a composable function in Compose for TV. You are going to start with defining a composable function for the media catalog browser as the following snippet:

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
// ToDo: add implementation
}

CatalogBrowser is the composable function implementing your media catalog browser. The function takes the following arguments:

  • List of featured content.
  • List of sections.
  • A Modifier object.
  • A callback function, which triggers a screen transition.

Set UI elements

Compose for TV offers lazy lists, a component to display a large number of items (or a list of an unknown length). You are going to call TvLazyColumn to place sections vertically. TvLazyColumn provides a TvLazyListScope.() -> Unit block, which offers a DSL to define item contents. In the following example, each section is placed in a vertical list with a 16 dp gap between sections.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  TvLazyColumn(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(16.dp)
  ) {
    items(sectionList) { section ->
      Section(section, onItemSelected = onItemSelected)
    }
  }
}

In the example, Section composable function defines how to display sections. In the following function, TvLazyRow demonstrates how this horizontal version of TvLazyColumn is similarly used to define a horizontal list with a TvLazyListScope.() -> Unit block by calling the provided DSL.

@Composable
fun Section(
  section: Section,
  modifier: Modifier = Modifier,
  onItemSelected: (Movie) -> Unit = {},
) {
  Text(
    text = section.title,
    style = MaterialTheme.typography.headlineSmall,
  )
  TvLazyRow(
     modifier = modifier,
     horizontalArrangement = Arrangement.spacedBy(8.dp)
  ) {
    items(section.movieList){ movie ->
    MovieCard(
         movie = movie,
         onClick = { onItemSelected(movie) }
       )
    }
  }
}

In the Section composable, the Text component is used. Text and other components defined in Material Design are offered in the tv-material library . You can change the texts' style as defined in Material Design by referring to the MaterialTheme object. This object is also provided by the tv-material library. MovieCard defines how each movie data is rendered in the catalog defined as the following snippet. Card is also a part of the tv-material library.

@Composable
fun MovieCard(
   movie: Movie,
   modifier: Modifier = Modifier,
   onClick: () -> Unit = {}
) {
   Card(modifier = modifier, onClick = onClick){
    AsyncImage(
       model = movie.thumbnailUrl,
       contentDescription = movie.title,
     )
   }
}

In the example described earlier, all movies are displayed equally. They have the same area, no visual difference between them. You can highlight some of them with Carousel.

Carousel displays the information in a set of items that can slide, fade, or move into view. You use the component to highlight featured content, such as newly available movies or new episodes of TV programs.

Carousel expects you to at least specify the number of items that Carousel has and how to draw each item. The first one can be specified with itemCount. The second one can be passed as a lambda. The index number of the displayed item is given to the lambda. You can determine the displayed item with the given index value.

@Composable
function FeaturedCarousel(
  featuredContentList: List<Movie>,
  modifier: Modifier = Modifier,
) {
  Carousel(
    itemCount = featuredContentList.size,
    modifier = modifier,
  ) { index ->
    val content = featuredContentList[index]
    Box {
      AsyncImage(
        model = content.backgroundImageUrl,
        contentDescription = content.description,
        placeholder = painterResource(
          id = R.drawable.placeholder
        ),
        contentScale = ContentScale.Crop,
        modifier = Modifier.fillMaxSize()
      )
      Text(text = content.title)
    }
  }
}

Carousel can be an item of a lazy list, such as TvLazyColumn. The following snippet shows FeaturedCarousel composable on top of the all Section composables.

@Composable
fun CatalogBrowser(
   featuredContentList: List<Movie>,
   sectionList: List<Section>,
   modifier: Modifier = Modifier,
   onItemSelected: (Movie) -> Unit = {},
) {
  TvLazyColumn(
    modifier = modifier.fillMaxSize(),
    verticalArrangement = Arrangement.spacedBy(16.dp)
  ) {

    item {
      FeaturedCarousel(featuredContentList)
    }

    items(sectionList) { section ->
      Section(section, onItemSelected = onItemSelected)
    }
  }
}