Amazon Fire TV Stick App in React Native

Authors
Jakub Kłobus
Software Engineer
@
Callstack

Our adventure with an AF has started from a request from a one of our client to build an app which would be responsible for displaying important information on TV displays in shops. Fire TV Stick is a small device (in the size of a bigger memory stick) which is probably the cheapest option to make non-smart TVs “smarter”. The most important thing for us is that it’s powered by Android.

When our AF arrived, we had to decide which technology stack we would like to choose. A final app would consist of 3–4 screens with input areas, list and details view.

How can we write an app for AF?

After some research we came up with the following options:

  • HTML5 app
  • native Android app
  • React Native app

The order of the list above is not a coincidence, the first option is probably the easiest and you can find relatively many docs about that solution. RN app is on the last position because we haven’t found anyone who had done this before. As it’s not too hard to spot the answer we wanted to try with RN on AF.

For the purpose of this article, we want to go through a process of creating an RSS reader in RN for AF.

How to enable adb?

The first step to become a React Native developer for Amazon Fire TV Stick is to enable adb on a device. On a Fire Stick, it’s even easier than on an Android phone. We just have to go to the device settings and find the adb switch. Next, we can verify whether the adb was enabled or not, we can do that from CLI (if our AF is connected to our computer) using command <rte-code>adb devices<rte-code>.

Will an empty app work?

Now we can generate and start an empty RN app, we have to run the following two commands:

  • <rte-code>react-native init FirestickDemo && cd FirestickDemo<rte-code>
  • <rte-code>react-native run-android<rte-code>

As a result, we should see a standard RN app, be able to show developers the menu and navigate through options. 🎉

Can we navigate through buttons?

At this point, it seems like we can start developing what we planned. Let’s add some buttons to test if it works.

export default class Buttons extends Component {
state = {
color: 'white',
}
render() {
return (
<View style={styles.container}>
<Button title="Button 1" onPress={() => this.setState({ color: 'red' })} color="red"/>
<Button title="Button 2" onPress={() => this.setState({ color: 'green' })} color="green"/>
<Button title="Button 3" onPress={() => this.setState({ color: 'blue' })} color="blue"/>
<View style={[styles.view, { backgroundColor: this.state.color }]} />
</View>
);
}
}

White rectangle should change background color.

Why can’t we click on anything?

We are clicking center button on remote control but nothing happens. It’s possible to navigate between touchable components and buttons but there is no possibility to call <rte-code>onPress<rte-code> method. We tried few different approaches. We even wrote native apps in Java and Native Script with few buttons (those apps works great). Turned out that <rte-code>Button<rte-code> component doesn’t use native button underneath. That’s an unfortunate.

How does AF’s remote work?

While writing our first app for AF we realized that remote control sends classic keyboard events. React Native doesn’t support those events by default but we found a library for that. Great, so there is still hope. It looks like we have everything we need — possibility to navigate between every <rte-code>touchable<rte-code> and possibility to listen to keyboard events. We could manually call <rte-code>onPress<rte-code> after user press center button on a remote control.

Yes, we could… but we can’t.

Another problem — there is no possibility to find out which <rte-code>touchable<rte-code> is focused. Ah, React Native, dear friend why you have to make everything harder. Now we must write something big and ugly that will become better <rte-code>touchable<rte-code> and keep in our state information about currently focused component.

How to live with that?

Our idea was to create two things:

  • <rte-code>SelectableContainer<rte-code> - a component which will store all information about selectable components in current view and react to keyboard events.
  • <rte-code>selectable<rte-code> - a Higher Order Component , something that can transform classic <rte-code>TouchableOpacity<rte-code>, <rte-code>Button<rte-code>, <rte-code>View<rte-code>, etc. into a component that can be focused and selected using a keyboard or a remote control.

<rte-code>SelectableContainer<rte-code> should be a stateful component with a state in that shape:

{
activeSelectable: ?Object;
selectables: Array<{
x: number;
y: number;
onFocus: Function;
onPress: Function;
onBlur: Function;
}>;
}
view raw state.js hosted with ❤ by GitHub

So we can properly react on every up/down button click and change <rte-code>activeSelectable<rte-code> to one above/below. There is a list of coordinates in a component state so we can easily find out which selectable should we select. On every change of <rte-code>activeSelectable<rte-code> we will call <rte-code>onFocus<rte-code>, <rte-code>onBlur<rte-code> or <rte-code>onPress<rte-code> method. The code which is responsible for all of that:

_handleKeyDown = (key: number) => {
switch (key) {
case keyCodes.up:
this._selectNewActive(x => x - 1);
break;
case keyCodes.down:
this._selectNewActive(x => x + 1);
break;
case keyCodes.center:
if (this.state.activeSelectable) {
this.state.activeSelectable.onPress();
}
break;
}
return true;
}
_selectNewActive(idxModifier: Function) {
if (this.state.activeSelectable) { // blur active Selectable
this.state.activeSelectable.onBlur();
}
const sortedSelectables = this.state.selectables.sort((a, b) => (a.y - b.y));
if (this.state.activeSelectable) { // select next Selectable
const idx = sortedSelectables.indexOf(this.state.activeSelectable);
const newIdx = idxModifier(idx || 0);
if (newIdx >= 0 && newIdx < sortedSelectables.length) {
sortedSelectables[newIdx].onFocus();
this.setState({ activeSelectable: sortedSelectables[newIdx] });
} else {
this.setState({ activeSelectable: null });
}
} else { // select first Selectable
sortedSelectables[0].onFocus();
this.setState({ activeSelectable: sortedSelectables[0] });
}
}

How does SelectableContainer have the access to buttons inside?

The answer could frighten you — through <rte-code>context<rte-code>. Yes, we know… it’s an experimental API which will probably change but still, it’s the best solution for us. Of course, we don’t want to write context “magic“ every time we want to add simple button. It will be ugly and hard to maintain.

That’s why we created <rte-code>selectable<rte-code> HOC. It will register our component in <rte-code>SelectableContainer<rte-code> and make sure that we can use <rte-code>onPress<rte-code> prop on every component wrapped by it. It will also read position of our component so <rte-code>SelectableContainer<rte-code> will know the proper visual order of selectable components.

Okay, we can now easily create selectable <rte-code>Button<rte-code> or <rte-code>View<rte-code>. Let’s do that by modifying our first attempt to AF buttons:

const SelectableButton = selectable(Button);
export default class Buttons extends Component {
state = {
color: 'white',
}
render() {
return (
<View style={styles.container}>
<SelectableContainer>
<SelectableButton title="Button 1" onPress={() => this.setState({ color: 'red' })} color="red" />
<SelectableButton title="Button 2" onPress={() => this.setState({ color: 'green' })} color="green" />
<SelectableButton title="Button 3" onPress={() => this.setState({ color: 'blue' })} color="blue" />
<View style={[styles.view, { backgroundColor: this.state.color }]} />
</SelectableContainer>
</View>
);
}
}

Now we can select buttons!

Will inputs work?

The first part of our work is done! We are ready to create a list of RSS feeds with buttons. Now we only have to add some possibility to create custom RSS feed. And there is no surprise, classic <rte-code>TextInput<rte-code> doesn’t work well on AF. It’s impossible to navigate from another component to it. Fortunately, we created <rte-code>selectable<rte-code> before so we can us it here. Just simply wrap <rte-code>TextInput<rte-code> into <rte-code>selectable<rte-code> and call <rte-code>textInput.focus()<rte-code> inside <rte-code>onPress<rte-code> function prop. Just like this:

import React, { Component } from 'react';
import { TextInput } from 'react-native';
import selectable from './selectable';
const Selectable = selectable(TextInput); // The most important line in this implementation!
export default class SelectableInput extends Component {
_input: any
_handleInputPress = () => {
this._input.focus();
}
handleSubmit = () => {
this._input.blur();
}
render() {
return (
<Selectable
onPress={this._handleInputPress}
onSubmitEditing={this.handleSubmit}
ref={i => (this._input = i ? i._wrappedComponent : null)}
{...this.props}
/>
);
}
}

Now we truly have all pieces we need to build AF app!

RSS feed app

As you can see our app consists of 2 screens, for navigation we use exponent/ex-navigation which handle back button for us. The most interesting part of an app (of course besides problems described above) was fetching RSS news in an easy to read from javascript form. Not that long time ago we would use Google Feed API to translate xmlish RSSes to jsons, but sadly Google decided to shut down this service and we had to find an alternative solution. Finally, we came across YQL (Yahoo query language) which works exactly as we expected. For displaying RSS content which sometimes is HTML we use WebView component from RN.

Link to source code.

Conclusion

Should you use React Native as a technology to build AF app? If you are familiar with React Native definitely yes, it was a great adventure for us and we learned a lot of things. If not, maybe stay with native stuff or write an app in HTML5 which is kind of a recommended solution for Fire TV Stick.

Insights

Learn more about TV

cover

Jun 11Article

AI-Assisted React Native Migration for TV: Lessons From Zattoo

A practical look at React Native TV migration, based on Zattoo’s experience with shared code, platform differences, and team change.

cover

Apr 27Webinar

From Native to React Native for TV: The Zattoo Case

Learn why and how Zattoo moved from native TV apps to React Native, what changed for their team, and what it takes to scale across multiple platforms efficiently.

cover

Nov 25Podcast

Amazon's Vega OS: A New OS With React Native Built-In

Giovanni Laquidara (Amazon) introduces Vega OS, a new lightweight operating system with React Native built-in, designed for high-performance on low-end media devices.

cover

Oct 3Article

Cross-Platform TV Apps With React Native for tvOS, Android TV, and Tizen

Learn why React Native is a powerful choice for TV app development. Discover how it unifies development for tvOS, Android TV, Tizen, and more with a single codebase.

cover

Oct 2Live Stream

Building React Native App for Vega OS

React 19.2 brings <Activity> and useEffectEvent, new ideas for React state management arrive, and Vega OS debuts as a React Native-powered Linux OS.

cover

May 29Talk

The Bigger Picture

React Native lessons from TV that scale: performance, architecture & cross-platform code strategies, beyond mobile, across devices.

cover

Mar 5Podcast

Multiple TV Platforms, One Codebase: React Native TV App Development

Is TV app development is the same as mobile, but bigger? It's not. In this episode, Michael Khirallah (DIRECTV) and Chris Trag (Amazon) will explain why.

cover

Dec 6Article

Developing a React Native TV App

Build React Native TV apps. Explore Expo and Bare React Native TV setups, add TV support to existing projects, and structure apps for multi-platform builds.

cover

Dec 13Podcast

React Native for TV App Development

Learn about the current state of the TV development space and the benefits and considerations of applying React Native to it.

cover

Oct 6Article

Receiving a Live Stream With Amazon IVS in React Native

Discover how to integrate Amazon IVS (Interactive Video Service) into your app, powered by AWS. Learn to set up a streaming player with minimal code.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.