Android TV: Getting Started

Learn how to create your first Android TV app! In this tutorial, you’ll create an Android TV app for viewing RayWenderlich Youtube channel videos! By Ivan Kušt.

Login to leave a rating/review
Download materials
Save for later

Learn how to create your first Android TV app! In this tutorial, you’ll create an Android TV app for viewing RayWenderlich Youtube channel videos!

Android TV differs a bit from Android phones. The main differences are showing the user interface (UI) on a large screen and needing to use a remote to navigate through options. In this tutorial, you’ll create an Android TV app for viewing YouTube channel videos!

In the process, you’ll learn:

  • What you have to do for your app to qualify as a TV app
  • What Android Leanback library is
  • How to create video catalog, details and playback screens
  • How to test your new app on Android TV emulator

Don’t worry, it’s easier than you think!

Note: This tutorial assumes you know the basics of Android development with Kotlin. If you’re new to Kotlin, check out our Kotlin introduction tutorial. If you’re completely new to Android development, read through our Beginning Android Development tutorials to familiarize yourself with the basics.

Getting Started

Android TV contains the Android system. If you know how to build layouts for Android Phones or Tablets, you’ll find building TV layouts pretty easy. TV apps have the same code structure as those for phones and tablets.

There are some caveats and requirements, though. Your app has to fill some criteria to be available from Play Store on Android TV. For example, you have to declare a TV Activity — more on that later.

To get started, download the Netderlix project using the Download Materials button at the top or bottom of this tutorial.

Open the project in Android Studio 4.1 or later by selecting Open an Existing Project on the Android Studio welcome screen or by selecting File ▸ Open if you have another project open already.

Wait for the project to sync. Then, build and run the project on a phone or existing phone emulator. For now, it’s fine to run the app this way because you’ll set up a TV emulator shortly.

Netderlix start screen on phone emulator, containing the words 'Hello! Replace me'

You see the main (video catalog) screen that you’ll fill with videos from YouTube channel.

Note: To build apps for TV, you need to have SDK tools version 24 or higher. Find instructions on how to update SDK tools in the official documentation.

Netderlix Code Structure

First, get familiar with the code. The following graph shows all the screens in the Netderlix app:

Netderlix app screens diagrams

There are three screens:

  • Catalog screen for picking a video implemented in CatalogFragment.
  • Video Details screen shown after the user picks a video implemented in VideoDetailsFragment. It also shows recommended videos the user can navigate to.
  • Video Playback screen implemented in VideoPlaybackFragment. It has standard playback controls for Play / Pause, Fast Forward / Rewind and Skip to the beginning and end of the video.

Code in the project is organized in the following way:

  • catalog package contains the code for the Catalog screen.
  • details package contains the code for the Details screen.
  • playback package contains the code for the Playback screen.
  • error package contains ErrorFragment for showing errors.
  • model package contains data classes that hold video and playlist data.
  • repository package contains PlaylistsRepository for fetching playlists and videos.
  • Util class in the root package contains utility methods.

Now, you’ll set up an Android TV emulator so you can test your new app. Feel free to skip the next section if you already have a TV emulator (or real Android TV) set up.

Setting up Android TV Emulator

The simplest way to test your Android TV app is to use an Android TV emulator provided by Android SDK. To set SDK up, select Tools ▸ SDK Manager in Android Studio.

SDK manager window showing updates and their installation status

Check Show Package Details to show detailed options for each Android version. Under Android 10.0 (Q), make sure you check Android TV Intel x86 Atom System Image.

SDK manager TV emulator selected

Tap OK, confirm changes on the dialog and wait for the download to complete if needed.

Now, you have to create an Android TV emulator to test on. Open Tools ▸ AVD Manager in Android Studio and click Create Virtual Device.

AVD Manager '+ Create Virtual Device' button

If you already have some emulators set, you see a bit different AVD manager screen.

AVD manager showing some emulators already set up

In the wizard, select TV under Category.

AVD manager showing list of device categories

Select Android TV (720p) and click Next. It emulates smaller TV screens so it uses less computer memory when running.

AVD manager 'Choose a device definition screen' with 'Android TV (720p)' option highlighted

On the next screen, select Android 10.0 and click Next.

AVD manager System Image menu with Android version Q selected

Finally, click Finish and wait a few seconds for the new device to set up.

AVD manager 'Verify Configuration' screen

Build and run on the new TV emulator.

Catalog screen showing 'Hello! Replace me' on black background

The Leanback Library

There are two key things to note when building user interfaces for TV devices:

  • You navigate the TV user interface by using a remote. This means your app must support D-pad input.
  • A TV screen is large and usually viewed from farther away.

Leanback library makes building UIs with these two things in mind easy.

Note: Leanback library includes a special theme — Theme.Leanback. It’s recommended to use it with Leanback classes. The theme doesn’t include an action bar because it’s not supported in TV apps. If you try to use AppCompatActivity, which sets up action bar, you get an error. For more information, check the official documentation.

To get a sense of how the Leanback library works, look at a diagram that shows the most important classes:

Leanback classes diagram

LeanbackFragment represents any of the Support fragments provided in the library. You’ll use three of them:

  • BrowseSupportFragment for implementing a screen with catalog browser
  • DetailsSupportFragment for implementing a screen with details
  • PlaybackSupportFragment for implementing a screen with video player

You can think of each of the fragments as a RecyclerView. It renders rows provided by an adapter. The adapter is a subclass of ObjectAdapter, which relies on a subclass of Presenter. It converts adapter items into View instances displayed in Fragment.

ObjectAdapter items can be any type if you provide Presenter implementation that knows how to convert that type to View.

There are two existing types of presenters:

  • RowPresenter renders Row objects into Views.
  • ListRowPresenter renders a special type of Row objects that contain a header and a sublist.

Meeting Android TV App Criteria

Now that you’ve learned about the Leanback library, it’s time to meet the requirements and make a bona fide Android TV app.

Open AndroidManifest.xml and replace category in intent-filter with the following:

<category android:name="android.intent.category.LEANBACK_LAUNCHER" />

To declare a TV launcher activity, you use android.intent.category.LEANBACK_LAUNCHER intent filter the same way you use android.intent.category.LAUNCHER.

Build and run.

Catalog screen showing 'Hello! Replace me' on black background

There’s no difference because the TV emulator lets you run the app without LEANBACK_LAUNCHER intent filter. On a real TV device, the app won’t appear without LEANBACK_LAUNCHER intent filter and Play Store won’t make your app available for TV devices.

The second requirement is to declare that the app uses the Leanback user interface required by Android TV.

Add the following tag right after opening manifest tag in AndroidManifest.xml:

  android:required="true" />

Here, you set the Leanback user interface.

Below the previous code, add:

      android:required="false" />

This declares that the app doesn’t require a touchscreen.

Note: If you don’t declare that the app uses Leanback and doesn’t require a touchscreen, Play Store won’t make it available for Android TV devices.

The last requirement is to add launcher banner. The drawable for launcher banner is already included in the project under res/drawable/banner.xml. To set it up, add a new property to application:


Build and run. Close the running app, open an app list and notice the new Netderlix app banner.

App banner in TV menu

Creating Video Catalog

The first screen of the Netderlix app is pretty empty at the moment. The idea is that it shows a catalog of available playlists and videos from the YouTube channel. The user can play and select each of the videos to see the details.

Using BrowseSupportFragment from Leanback

Open CatalogFragment.kt. It already extends BrowseSupportFragment and contains code to initialize the YouTube API for fetching videos and playlists. It’s hosted in CatalogActivity and is set up to launch when the app starts on TV.

The first thing to do is to form the title and headers. In CatalogFragment.kt, create setupTitleAndHeaders():

private fun setupTitleAndHeaders() {
  // 1
  title = getString(R.string.browse_title)

  // 2
  headersState = HEADERS_ENABLED
  isHeadersTransitionOnBackEnabled = true

  // 3
  brandColor = ContextCompat.getColor(

Now, go over it step by step:

  1. You set the title. It’s a property in BrowseSupportFragment.
  2. You enable headers in the left section and define back navigation. When you select an item from the catalog, headers hide and you can press Back to show headers again.
  3. You set brand color, which is the color of the headers background.

Call it from onActivityCreated() before calling loadPlaylists(). Your code will look like this:

Call site of the setupTitleAndHeaders function

Build and run. Notice the added title and new color of the section on the left.

Catalog screen showing title at top right and header with green background on left

Implementing a Presenter for Showing Video Thumbnails

As mentioned before, you can imagine as if CatalogFragment contains a RecyclerView. The Leanback library uses a subclass of Presenter to render the contents of each item. Presenter manages creating and binding view holders for ObjectAdapter. Each video item in the catalog will be an item of type VideoItem.

Open CatalogCardPresenter.kt and create custom Presenter. There are three functions you have to override:

  • onCreateViewHolder() for creating a new ViewHolder
  • onBindViewHolder() for binding ViewHolder to an item
  • onUnbindViewHolder() for unbinding ViewHolder from an item

The first one is already implemented. It initializes colors and sets up the background.

Add the following to onBindViewHolder():

val videoItem = item as VideoItem
val video =

val cardView = viewHolder?.view as ImageCardView
cardView.titleText = video.title
cardView.contentText =
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)


Following this code:

  1. The argument item is cast to VideoItem. Then, you create a local Video object that stores video data.
  2. You fetch View of ViewHolder and cast it to ImageCardView. Next, you set its title text, content text and dimensions.
  3. Using loadBitmapIntoImageView() from Util.kt, you use a local drawable and load it into cardView.

Add this to onUnbindViewHolder():

val cardView = viewHolder?.view as ImageCardView
// Remove references to images so that the garbage collector can free up memory
cardView.badgeImage = null
cardView.mainImage = null

This clears the image from ImageCardView so garbage collector can free occupied memory.

Build project to make sure there are no compile errors.

Creating Video Catalog With Playlists

The next thing to do is to fill the catalog with videos.

Go back to CatalogFragment.kt and find loadAndShowPlaylists(). Replace all the code from its body with:

val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CatalogCardPresenter()

This code creates an adapter for catalog rows and a custom presenter.

To make loading of YouTube API playlists easier, find loadPlaylists() and notice it takes two arguments: a success function for providing results and an error function.

The success function has three arguments:

  • Playlist object that contains information about the playlist
  • ArrayList of VideoObject that contains information about videos
  • Boolean value that confirms the last playlist in the batch

The system calls success function for each fetched playlist. Playlists come in batches (or pages) so if the current playlist is the last one in the batch, the last argument is set to true.

Add the following lambda at the end of loadAndShowPlaylists():

val onPlaylistLoaded = { playlist: Playlist,
    videos: ArrayList<Video>,
    lastPlaylistInBatch: Boolean ->

  // 1
  val listRowAdapter = ArrayObjectAdapter(cardPresenter)

  // 2
  videos.forEach { video ->
    listRowAdapter.add(VideoItem(video, videos))

  // 3
  val header = HeaderItem(rowsAdapter.size().toLong(), playlist.title)
  rowsAdapter.add(ListRow(header, listRowAdapter))

  // 4
  if (lastPlaylistInBatch) {

Following this code, you:

  1. Create a new instance of ArrayObjectAdapter that uses cardPresenter.
  2. Populate an item list for adapter with all videos of the playlist.
  3. Create a header for the row that will represent the playlist. Then, you define ListRow object with both the header and adapter containing the videos and pass the object to rowsAdapter.
  4. Hide progress bar if this is the last playlist in the batch.

Add this code at the end of loadAndShowPlaylists():
loadPlaylists(onPlaylistLoaded, ::showError)

Here, you start the progress bar and video loading. Note that you’re passing showError() as a second argument. It’s provided in the starter project and shows a fragment with an error message.

Finally, below the previous code add:

adapter = rowsAdapter

This assigns the newly created rowsAdapter to CatalogFragment adapter.

Build and run. You’ll get an error message because the YouTube API key isn’t set up yet.

App showing 'Error communicating with Youtube API' message

Generating YouTube API Key

Now, follow the steps in the official documentation to get your YouTube API key.

After you’ve set up the YouTube API key, open your file and add the following line:


Build and run again. If you attached the key properly, you see all the videos from the channel!

App catalog screen with links to videos on the right and their titles on the left

Looks awesome, doesn’t it?

Note: If you still receive an error make sure that you emulator is able to access the internet and your youtube API key is enabled.

Last, in CatalogFragment.kt, call initializeBackground() at the end of onActivityCreated(). Your code will look like this:

InitializeBackground call site

Build and run. You can see the background changing as you select videos.

App catalog screen with background set up

Setting up Click Listeners

Once the video card is selected, you need to show its details. To set up listeners, add the following in onActivityCreated():

onItemViewClickedListener = 
  OnItemViewClickedListener { itemViewHolder, item, _, _ ->
    if (item is VideoItem) {
      showVideoDetails(requireActivity(), itemViewHolder, item)

This listener checks if the clicked item is VideoItem. If so, showVideoDetails() creates Intent for starting VideoDetailsActivity.kt and sets up a shared transition for the thumbnail.

Note: For more details on makeSceneTransitionAnimation() in Util.kt, check the official documentation.

Build and run. Select a video, and you’ll see a black screen. This will be your next task.

Blank details screen

Presenting Video Details

Leanback provides DetailsSupportFragment, which makes setting up video details screen easy.

Creating Details View for Selected Playlist

Open VideoDetailsFragment.kt and notice it already extends DetailsSupportFragment. Its view contains two data rows, which you’ll implement now.

First, add a function that creates an adapter for actions available for the video:

private fun getActionAdapter() = ArrayObjectAdapter().apply {

The function creates an instance of ArrayObjectAdapter with a single Action item. The item represents action to watch the video that also passes the respective title and subtitle.

Now, create a function that creates a details row:

private fun createDetailsOverviewRow(
  selectedVideo: Video, 
  detailsAdapter: ArrayObjectAdapter
): DetailsOverviewRow {

  val context = requireContext()

  // 1
  val row = DetailsOverviewRow(selectedVideo).apply {
    imageDrawable = ContextCompat.getDrawable(context, R.drawable.default_background)
    actionsAdapter = getActionAdapter()

  // 2
  val width = resources.getDimensionPixelSize(R.dimen.details_thumbnail_width)
  val height = resources.getDimensionPixelSize(R.dimen.details_thumbnail_height)

  // 3
  loadDrawable(requireActivity(), selectedVideo.cardImageUrl, R.drawable.default_background, width, height)
  { resource ->
    row.imageDrawable = resource
    // 4
    detailsAdapter.notifyArrayItemRangeChanged(0, detailsAdapter.size())

  return row

This function creates the first data row. It contains details for the video: thumbnail, description and action for playing the video.

Here, you:

  1. Create a new instance of DetailsOverviewRow and set default video thumbnail and play action.
  2. Get the width and height for the thumbnail drawable.
  3. Load a drawable using loadDrawable() from Util.kt. If loading is successful, you show the loaded image.
  4. Notify the details adapter that held data has changed.

Then, add a function that handles actions:

private fun onActionClicked(action: Action, videoItem: VideoItem) {
  if ( == ACTION_WATCH) {
    val intent = VideoPlaybackActivity.newIntent(requireContext(), videoItem)

It checks whether the action has ID ACTION_WATCH and, if it does, starts VideoPlaybackActivity.

Now, create a function that creates a presenter for the details row:

private fun createDetailsOverviewRowPresenter(
  videoItem: VideoItem,
  actionHandler: (Action, VideoItem) -> Unit
): FullWidthDetailsOverviewRowPresenter =
  // 1
   FullWidthDetailsOverviewRowPresenter(DetailsDescriptionPresenter()).apply {
    // 2
    backgroundColor =
        ContextCompat.getColor(requireContext(), R.color.selected_background)

    // 3
    val sharedElementHelper = FullWidthDetailsOverviewSharedElementHelper()
    isParticipatingEntranceTransition = true

    // 4
    onActionClickedListener = OnActionClickedListener { 
      actionHandler(it, videoItem) 

Through these steps, you:

  1. Create an instance of FullWidthDetailsOverviewRowPresenter and configure it in apply below
  2. Set the background color
  3. Set up shared transition with Catalog screen
  4. Set a click listener

Finally, create a function:

private fun createPresenterSelector(videoItem: VideoItem) = 
  ClassPresenterSelector().apply {
    // 1
        createDetailsOverviewRowPresenter(videoItem, ::onActionClicked)

    // 2

This function returns ClassPresenterSelector with a proper presenter instance depending on the class of the rendering item:

  1. DetailsOverviewRowPresenter created by createDetailsOverviewRowPresenter() for DetailsOverivewRow items.
  2. ListRowPresenter for ListRow items.

Now, override onActivityCreated() and add the following code:

override fun onActivityCreated(savedInstanceState: Bundle?) {

  val videoItem = arguments?.getSerializable(ARGUMENT_VIDEO) as VideoItem

Here, you get VideoItem passed from arguments.

Then, add the code at the end of onActivityCreated():

adapter = ArrayObjectAdapter(createPresenterSelector(videoItem)).apply {
  add(createDetailsOverviewRow(, this))

With these lines, you create and assign an adapter for VideoDetailsFragment.

Build and run. Select a video, and you’ll see the new details screen.

App details screen showing video's title, source and brief description

Previewing Recommendations

Add a new function for creating recommendations to VideoDetailsFragment.kt:

private fun createRelatedVideosRow(videoItem: VideoItem): ListRow {
  // 1
  val selectedVideo =
  val playlistVideos = videoItem.playlist

  // 2
  val recommendations = ArrayList(playlistVideos.filterNot { it == selectedVideo })

  // 3
  val listRowAdapter = ArrayObjectAdapter(CatalogCardPresenter())
  val header = HeaderItem(0, getString(R.string.related_videos))

  if (recommendations.isNotEmpty()) {
    for (i in 0 until NUM_RECOMMENDATIONS) {
          VideoItem(recommendations.random(), playlistVideos)

  // 4
  return ListRow(header, listRowAdapter)

Here is the explanation. You:

  1. Get the selected video and list of all videos in the corresponding playlist from VideoItem
  2. Construct recommendations by filtering out selected video from the playlist videos
  3. Create an adapter that holds all the recommendations and fill it with items that represent recommendations
  4. Wrap the new adapter in ListRow and return it

Add the following line at the end of the apply block in onActivityCreated():


Build and run. Open details for a video and navigate down to see recommended videos.

App details screen showing video recommendations

Previewing Recommendation Video Details

Set an item click listener by adding the following code to the end of onActivityCreated():

onItemViewClickedListener = OnItemViewClickedListener { itemViewHolder, item, _, _ ->
  if(item is VideoItem) {
    showVideoDetails(requireActivity(), itemViewHolder, item)

In this code, you present video details just as you did for catalog videos.

Build and run. Select a video recommendation to see the details of that video.

App details screen showing video's title, source and brief description

Setting Details View Background

To add a final touch to the details screen, call initializeBackground() at the end of onActivityCreated():


Build and run. You’ll see the background on the details screen now.

App details screen with background set up

Select any video from the catalog and open its details. Then, select Watch Now For Free.

You’ll see an empty screen for now.

Empty video playback screen

Playing a Video

And now for the most important part — playing the video.

Leanback provides two useful Fragments for implementing a playback screen:

  • PlaybackSupportFragment: A fragment for displaying playback controls and video
  • VideoSupportFragment: A subclass of PlaybackSupportFragment that provides SurfaceView for rendering the video

The only supported way of playing YouTube videos currently is using its IFrame player from WebView.

Note: If you’re interested in YouTube’s IFrame player, you can find more details here.

You’ll use a library that wraps the IFrame player into View.

Introducing the Glue Mechanisms

Leanback has a specific way of separating UI video playback controls from the video player. The following diagram shows the classes:

Glue classes diagram

PlaybackSupportFragment provides the basic functionality of a video playback screen. It uses PlaybackTransportControlGlue and PlaybackSupportFragmentGlueHost to communicate with PlayerAdapter. PlayerAdapter is a wrapper for a class (or more classes) for playing a video. PlaybackTransportControlGlue handles displaying playback controls and communicating with PlayerAdapter.

This setup looks complicated at first, but it just separates video player logic from Fragment that displays video and controls.

To add support for your video player, you extend PlayerAdapter and PlaybackTransportControlGlue. For playing YouTube videos, you’ll use EmbeddedPlayerAdapter, which is provided in the project.

First, you have to finish implementing VideoPlaybackControlGlue, which communicates with EmbeddedPlayerAdapter.

Open VideoPlaybackControlGlue.kt and add fields that hold playback actions:

private lateinit var skipPreviousAction: PlaybackControlsRow.SkipPreviousAction
private lateinit var skipNextAction: PlaybackControlsRow.SkipNextAction
private lateinit var fastForwardAction : PlaybackControlsRow.FastForwardAction
private lateinit var rewindAction : PlaybackControlsRow.RewindAction

Then, initialize actions in onCreatePrimaryActions(). Add this function at the end of the class:

override fun onCreatePrimaryActions(primaryActionsAdapter: ArrayObjectAdapter?) {

  // 1
  skipPreviousAction = PlaybackControlsRow.SkipPreviousAction(context)
  rewindAction = PlaybackControlsRow.RewindAction(context)
  fastForwardAction = PlaybackControlsRow.FastForwardAction(context)
  skipNextAction = PlaybackControlsRow.SkipNextAction(context)

  // 2
  primaryActionsAdapter?.apply {

Leanback calls this function when it creates a row of primary video controls. Here’s what it’s doing:

  1. Creates new instances of Skip Previous, Rewind, Fast Forward and Skip Next actions.
  2. Adds created actions to an adapter for controls row.

Add the following line at the end of next():

This calls next() from EmbeddedPlayerAdapter once the user clicks the Skip Next action.

And add the following at the end of previous():


This line calls previous() once the user clicks the Skip Previous action.

Add the following to onActionClicked():

when(action) {
  rewindAction -> playerAdapter.rewind()
  fastForwardAction -> playerAdapter.fastForward()
  else -> super.onActionClicked(action)

This handles the remaining actions. If the action is not recognized, let the super implementation handle it.

Now, open VideoPlaybackFragment.kt and add this code to the end of onCreate():

playerGlue = VideoPlaybackControlGlue(
).apply {
  host = PlaybackSupportFragmentGlueHost(this@VideoPlaybackFragment)

  isControlsOverlayAutoHideEnabled = true

  title =
  subtitle =

With this code, you:

  1. Pass activity reference and instance of EmbeddedPlayerAdapter to VideoPlaybackControlGlue. This is the wrapper for the YouTube IFrame player that VideoPlaybackControlGlue uses to link video controls with IFrame player.
  2. Set PlaybackSupportFragmentGlueHost as host and controls to be auto-hidden.
  3. Set a video title and subtitle.
Note: Check onCreateView() to see initialization of the YouTubePlayerView. For more information on all the available properties, you can check android-youtube-player library.

Now, build and run and start a video!

App video playback screen showing a man speaking into a microphone and a description from Apple of what Combine does

That’s it! You’ve just created your first Android TV app. Congrats! :]

Where to Go From Here?

You can download the complete project using the Download Materials button at the top or bottom of this tutorial.

If you want to know more about this topic, make sure to check out the official documentation for building Android TV apps.

Also make sure to check other available libraries for Android TV.

Hopefully, you’ve enjoyed this tutorial on creating an Android TV app. If you have any comments or questions, please join the forum discussion below!