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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android TV: Getting Started
25 mins
- Getting Started
- Netderlix Code Structure
- Setting up Android TV Emulator
- The Leanback Library
- Meeting Android TV App Criteria
- Creating Video Catalog
- Using BrowseSupportFragment from Leanback
- Implementing a Presenter for Showing Video Thumbnails
- Creating Video Catalog With Playlists
- Setting up Click Listeners
- Presenting Video Details
- Creating Details View for Selected Playlist
- Previewing Recommendations
- Previewing Recommendation Video Details
- Setting Details View Background
- Playing a Video
- Introducing the Glue Mechanisms
- Where to Go From Here?
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 {
add(
Action(
ACTION_WATCH,
resources.getString(R.string.watch_action_title),
resources.getString(R.string.watch_action_subtitle)
)
)
}
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:
- Create a new instance of
DetailsOverviewRowand set default video thumbnail and play action. - Get the width and height for the thumbnail drawable.
- Load a drawable using
loadDrawable()from Util.kt. If loading is successful, you show the loaded image. - 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.id == ACTION_WATCH) {
val intent = VideoPlaybackActivity.newIntent(requireContext(), videoItem)
startActivity(intent)
}
}
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()
sharedElementHelper.setSharedElementEnterTransition(
activity,
VideoDetailsActivity.SHARED_ELEMENT_NAME
)
setListener(sharedElementHelper)
isParticipatingEntranceTransition = true
// 4
onActionClickedListener = OnActionClickedListener {
actionHandler(it, videoItem)
}
}
Through these steps, you:
- Create an instance of
FullWidthDetailsOverviewRowPresenterand configure it inapplybelow - Set the background color
- Set up shared transition with Catalog screen
- Set a click listener
Finally, create a function:
private fun createPresenterSelector(videoItem: VideoItem) =
ClassPresenterSelector().apply {
// 1
addClassPresenter(
DetailsOverviewRow::class.java,
createDetailsOverviewRowPresenter(videoItem, ::onActionClicked)
)
// 2
addClassPresenter(
ListRow::class.java,
ListRowPresenter()
)
}
This function returns ClassPresenterSelector with a proper presenter instance depending on the class of the rendering item:
-
DetailsOverviewRowPresenter created by
createDetailsOverviewRowPresenter()forDetailsOverivewRowitems. -
ListRowPresenter for
ListRowitems.
Now, override onActivityCreated() and add the following code:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
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(videoItem.video, 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.
Previewing Recommendations
Add a new function for creating recommendations to VideoDetailsFragment.kt:
private fun createRelatedVideosRow(videoItem: VideoItem): ListRow {
// 1
val selectedVideo = videoItem.video
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) {
listRowAdapter.add(
VideoItem(recommendations.random(), playlistVideos)
)
}
}
// 4
return ListRow(header, listRowAdapter)
}
Here is the explanation. You:
- Get the selected video and list of all videos in the corresponding playlist from
VideoItem - Construct recommendations by filtering out selected video from the playlist videos
- Create an adapter that holds all the recommendations and fill it with items that represent recommendations
- Wrap the new adapter in
ListRowand return it
Add the following line at the end of the apply block in onActivityCreated():
add(createRelatedVideosRow(videoItem))
Build and run. Open details for a video and navigate down to see recommended videos.
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.
Setting Details View Background
To add a final touch to the details screen, call initializeBackground() at the end of onActivityCreated():
initializeBackground(videoItem.video)
Build and run. You’ll see the background on the details screen now.
Select any video from the catalog and open its details. Then, select Watch Now For Free.
You’ll see an empty screen for now.
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
PlaybackSupportFragmentthat providesSurfaceViewfor rendering the video
The only supported way of playing YouTube videos currently is using its IFrame player from WebView.
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:
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?) {
super.onCreatePrimaryActions(primaryActionsAdapter)
// 1
skipPreviousAction = PlaybackControlsRow.SkipPreviousAction(context)
rewindAction = PlaybackControlsRow.RewindAction(context)
fastForwardAction = PlaybackControlsRow.FastForwardAction(context)
skipNextAction = PlaybackControlsRow.SkipNextAction(context)
// 2
primaryActionsAdapter?.apply {
add(skipPreviousAction)
add(rewindAction)
add(fastForwardAction)
add(skipNextAction)
}
}
Leanback calls this function when it creates a row of primary video controls. Here’s what it’s doing:
- Creates new instances of Skip Previous, Rewind, Fast Forward and Skip Next actions.
- Adds created actions to an adapter for controls row.
Add the following line at the end of next():
playerAdapter.next()
This calls next() from EmbeddedPlayerAdapter once the user clicks the Skip Next action.
And add the following at the end of previous():
playerAdapter.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():
//1
playerGlue = VideoPlaybackControlGlue(
requireActivity(),
EmbeddedPlayerAdapter(lifecycleScope)
).apply {
//2
host = PlaybackSupportFragmentGlueHost(this@VideoPlaybackFragment)
isControlsOverlayAutoHideEnabled = true
//3
title = videoItem.video.title
subtitle = videoItem.video.description
}
With this code, you:
- Pass activity reference and instance of
EmbeddedPlayerAdaptertoVideoPlaybackControlGlue. This is the wrapper for the YouTube IFrame player thatVideoPlaybackControlGlueuses to link video controls with IFrame player. - Set
PlaybackSupportFragmentGlueHostas host and controls to be auto-hidden. - Set a video title and subtitle.
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!
That’s it! You’ve just created your first Android TV app. Congrats! :]





