DEV Community

Rob Lauer for Progress

Posted on • Originally published at nativescript.org

Building an Android TV App with NativeScript

If you're here, I'm guessing NativeScript is your first choice (or will be soon!) when it comes to developing native cross-platform apps from one codebase, for both iOS and Android. I suppose the title gives it away, but did you know you can also build NativeScript apps for Android TV?

nativescript and android tv

While Android TV isn't taking the world by storm (yet) and we're not officially supporting this integration (yet), it's still an interesting exercise to consider as another channel for your killer app dev skills.

So what is Android TV? An Android TV is a set-top box (powered by Android) that lets you stream content to your TV 📺. In many ways, you can compare it to Apple TV, without the hardware restrictions inherent to Apple's ecosystem. The most common apps on Android TV focus on media consumption and gaming, but this platform is just waiting for its next killer app.

Maybe the time is ripe for another run at WebTV!? 😉

webtv

A Little History

One day we received a message from a NativeScript Sidekick user who wanted to leverage his NativeScript skills for an Android TV app:

android tv request via intercom

It was a bit of a 💡 moment for us, as we realized this platform deserved an unofficial home here at NativeScript HQ. Enter our old pal Eddy "the plugin" Verbruggen.

In no time at all, Eddy put together a POC to effectively offer Android TV as another build target for your NativeScript app. Watch the brief video here..

You know what he had to do? It's shockingly simple. In order to achieve raw compatibility with Android TV, you just need to add one line to your AndroidManifest.xml file:

<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
Enter fullscreen mode Exit fullscreen mode

But just getting an app to build and run on Android TV is one thing. What about embracing the unique capabilities of the platform?

Consult this GitHub repository for a basic, but fully-functional, NativeScript Android TV app example.

The D-Pad Controls

Unlike an iOS or Android device that allows you to use your fingers to control a UI, with Android TV you are navigating with a little remote control (the D-pad or directional pad). Therefore you really need the ability to:

  • Navigate the UI with a D-pad control, and
  • Be able to actively see where you are in the UI.

android tv ui example

This means making some minor UI adjustments to your NativeScript app. Luckily you have a couple of valid options:

Selectors

You can use Android Selectors to specifically set view states for your UI elements:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
      <item android:state_pressed="true" android:drawable="@drawable/bluebutton_focused"/> <!-- pressed -->
      <item android:state_focused="true" android:drawable="@drawable/bluebutton_focused"/> <!-- focused -->
      <item android:state_hovered="true" android:drawable="@drawable/bluebutton_focused"/> <!-- hovered -->
      <item android:drawable="@drawable/bluebutton"/> <!-- default -->
    </selector>
Enter fullscreen mode Exit fullscreen mode

While this does work perfectly fine, it's not really the NativeScript way. However, it works and wiring a selector is not too painful:

    export function elementLoaded(args: observable.EventData): void {
      const view = <ViewBase>args.object;

      // use a resource that specifies a 'focused' state:
      view.android.setBackgroundResource(identifier);
    }
Enter fullscreen mode Exit fullscreen mode

KeyEvents

Arguably a more proper way to implement a "focused" state is with Android KeyEvents:

      public dispatchKeyEvent(event: android.view.KeyEvent): boolean {
        // you can respond to specific keycodes by fi. registering a listener and invoking it when appropriate
        console.log("D-Pad center button pressed? " + (event.getKeyCode() === android.view.KeyEvent.KEYCODE_DPAD_CENTER));

        // let's highlight the element that currently has the focus
        const tnsButton = <ViewBase>this.getCurrentFocus()["jsview"];
        if (tnsButton && tnsButton !== this.highlightedElement) {
          tnsButton.addPseudoClass("focused");
          if (this.highlightedElement) {
            this.highlightedElement.deletePseudoClass("focused");
          }
          this.highlightedElement = tnsButton;
        }
        return super.dispatchKeyEvent(event);
      }
Enter fullscreen mode Exit fullscreen mode

...which you can wire up in your UI with:

    export function elementLoaded(args: observable.EventData): void {
      const view = <ViewBase>args.object;

      // set a backreference so 'dispatchKeyEvent' in app.ts can swap CSS classes
      view.android["jsview"] = args.object;
    }
Enter fullscreen mode Exit fullscreen mode

...with what is effectively a focused pseudo-class in your CSS:

    .nav button {
      background-color: #87cefa;
    }

    .nav button:focused {
      background-color: #65aafa;
    }
Enter fullscreen mode Exit fullscreen mode

The above example is a good opportunity to build a new NativeScript plugin, hint hint...

Separate UI Views

With NativeScript, you've always been able to have separate UI views by platform. With a clever little hack, you can also have a separate Android TV view:

    import * as utils from "tns-core-modules/utils/utils";

    if (utils.ad) {
      // Android: Load either the TV or phone UI
      const uiModeManager = utils.ad.getApplicationContext().getSystemService(android.content.Context.UI_MODE_SERVICE);
      if (uiModeManager.getCurrentModeType() === android.content.res.Configuration.UI_MODE_TYPE_TELEVISION) {
        console.log("Running on a TV");
        application.start({moduleName: "main-page-tv"});
      } else {
        console.log("Running on a Phone / Tablet");
        application.start({moduleName: "main-page"});
      }
    } else {
      // iOS
      application.start({moduleName: "main-page"});
    }
Enter fullscreen mode Exit fullscreen mode

Remember, you can find a fully-functional NativeScript Android TV app in this GitHub repository.

Wrapping Up

Now it's your turn! Try taking a simple NativeScript app and add these customizations. Deploy the app to an Android TV emulator (available as part of the Android SDK) to test it out.

Finally, let us know about your experience in the comments (or submit an issue) so we can continue to fine-tune this solution for Android TV developers.

Top comments (5)

Collapse
 
bytet profile image
Bytet • Edited

I found a browser shell app I got from appsgeyser could be side loaded to my FireTV but there's no cursor as it's intended to be used with an Android touch interface. I used js-spacial-navigation from GitHub and had great success. Anyone here play with this? I have one small pesky problem when moving backwards from page to page. Previous last focused elements are gone when returning to the page and the system is intialized. You can used the directional arrow keys and enter key to mimic the d-pad controls at loopzine.tv/firetv.htm first the listed issue link is active and all videos at that issue are active in this limited demo.

Collapse
 
andreslopezrm profile image
Andres Lopez

Very interesting article, NativeScript is powerful I would like to know more about the subject 😄

Collapse
 
mobdro8 profile image
Mobdro

I think it is possible and you can watch an online TV channel at mobdro official website.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.