Hello Android TV

Photo by Jens Kreuter on Unsplash

I recently got a chance to work on an Android TV app. I was expecting the same ball game, the regular stuff we do on a daily basis. Activity, fragment, network operations, animations etc. But it turned out there is more to it.

To the uninitiated in the Android TV world. Stop making assumptions as I made. There is more to it. Say hello to “Leanback” Google’s library for helping us build awesome tv.

Some useful links for reference

A lot to read there. Let’s dive in make a simple Android Tv app.

All sample code on https://github.com/sunwicked/androidtv

We start with selecting a new project, yes there is a tv section I hardly visited.

Initial setup has been done doing a test launch.

What just happened? Lot more than your regular hello world.

Basically a full-fledged Tv app with the recommended structure.

Let’s take a quick look at what do we have.

MainActivity has a MainFragment in layout which extends BrowseFragment. But it is shown as deprecated.

A quick jump in the documentation shows deprecated in API level 27.1.0. It is now recommended to use BrowseSupportFragment in place of BrowseFragment.

Similarly, there are more fragments in the sample for building for tv.

  • DetailsFragment, again deprecated. Use DetailsSupportFragment.
  • VideoSupportFragment
  • ErrorFragment, again deprecated. Use ErrorSupportFragment.

All of these are part of the leanback library.

Gradle dependencies

One more thing, notice Glide is added by default along with other libraries.

Let’s get the hello world in place.

What more do we have?

BrowseFragment onActivityCreated has calls to 4 methods





We are not going to explore all of them but in loadRows you will find CardPresenter which extends Presenter. This defines how the image cards will appear.

class CardPresenter : Presenter() {
private var mDefaultCardImage: Drawable? = null
private var sSelectedBackgroundColor: Int by Delegates.notNull()
private var sDefaultBackgroundColor: Int by Delegates.notNull()

override fun onCreateViewHolder(parent: ViewGroup): Presenter.ViewHolder {
Log.d(TAG, "onCreateViewHolder")

sDefaultBackgroundColor = ContextCompat.getColor(parent.context, R.color.default_background)
sSelectedBackgroundColor = ContextCompat.getColor(parent.context, R.color.selected_background)
mDefaultCardImage = ContextCompat.getDrawable(parent.context, R.drawable.movie)

val cardView = object : ImageCardView(parent.context) {
override fun setSelected(selected: Boolean) {
updateCardBackgroundColor(this, selected)

cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
updateCardBackgroundColor(cardView, false)
return Presenter.ViewHolder(cardView)

override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, item: Any) {
val movie = item as Movie
val cardView = viewHolder.view as ImageCardView

Log.d(TAG, "onBindViewHolder")
if (movie.cardImageUrl != null) {
cardView.titleText = movie.title
cardView.contentText = movie.studio
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)

override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) {
Log.d(TAG, "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

private fun updateCardBackgroundColor(view: ImageCardView, selected: Boolean) {
val color = if (selected) sSelectedBackgroundColor else sDefaultBackgroundColor
// Both background colors should be set because the view's background is temporarily visible
// during animations.

companion object {
private val TAG = "CardPresenter"

private val CARD_WIDTH = 313
private val CARD_HEIGHT = 176

There is a lot more to this. We will dig more in part 2.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store