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, intuitive, and visually pleasing and engaging.

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.

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

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

Create a composable function for catalog

Everything appearing on a display is implemented as a composable function in Compose for TV. Start by defining a composable function for the media catalog browser:

@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). Call LazyColumn to place sections vertically. LazyColumn provides a LazyListScope.() -> 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 = {},
) {
  LazyColumn(
    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, LazyRow demonstrates how this horizontal version of LazyColumn is similarly used to define a horizontal list with a LazyListScope.() -> 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,
  )
  LazyRow(
     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. Card is part of the tv-material library. MovieCard defines how each movie data is rendered in the catalog defined as the following snippet:

@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)
    }
  }
}