Skip to content

raphaelssampaio/TVTest

Repository files navigation

TVTest

A study project exploring TV app development with react-native-tvos — sidebar navigation, D-pad focus management, and React Navigation on TV hardware.

What this is

This isn't a product — it's a hands-on exploration of what changes when you build for the living room instead of a phone. The app is a minimal streaming-style shell: a persistent left sidebar (Home, Movies, Series, Games, Settings) and a content area that swaps between placeholder screens via @react-navigation/native-stack. The goal was to work through the TV-specific APIs that don't exist on mobile — D-pad focus, TVFocusGuideView, useTVEventHandler — and understand their performance implications on actual low-end TV hardware, not just a simulator.

Devices tested

  • Nvidia Shield (Android TV) — high-end reference device, used to confirm intended behavior.
  • Amazon Fire TV Stick — low-end hardware, used as the realistic performance baseline. If it's smooth here, it's smooth everywhere.

Key TV concepts covered

  • D-pad focus primitiveshasTVPreferredFocus to claim initial focus on mount (used on the first sidebar item and on each screen's primary button), onFocus/onBlur to drive visual focus state, and nextFocus* props (nextFocusUp/Down/Left/Right) for cases where the default focus engine's spatial guessing picks the wrong neighbor.
  • TVFocusGuideView with autoFocus — wraps the sidebar so the focus engine always has a deterministic entry point into that subtree instead of relying on geometry alone.
  • Isolated useTVEventHandler — the remote's raw key events are consumed in a single leaf component (EventBar) instead of at the app root. Every D-pad press fires this hook; subscribing at the root would re-render the entire tree (sidebar, content, navigator) on every keypress. Isolating it keeps re-renders scoped to one small component.
  • FlatList for virtualized carousels — TV hardware, especially Fire TV Stick-class devices, has a fraction of the GPU/CPU budget of a modern phone. Virtualization is not optional polish here; an unvirtualized horizontal list of cards is a real, visible jank source on low-end boxes.
  • Animated.spring with useNativeDriver: true — focus scale/highlight transitions run on the native UI thread. Without useNativeDriver, every focus change would bounce through the JS thread, which is also busy handling navigation and event-bar state — a guaranteed way to drop frames during fast D-pad navigation.

Project structure

.
├── App.tsx                       # Root layout: sidebar + EventBar + nav host
├── index.js                      # Entry point, enableScreens()
├── src/
│   ├── components/
│   │   ├── FocusableCard.tsx     # Shared focus/animation logic (TouchableHighlight + Animated.spring)
│   │   └── PlaceholderScreen.tsx # Shared placeholder layout used by every screen
│   ├── navigation/
│   │   └── AppNavigator.tsx      # Native-stack navigator, headerShown: false
│   └── screens/
│       ├── HomeScreen.tsx
│       ├── MoviesScreen.tsx
│       ├── SeriesScreen.tsx
│       ├── GamesScreen.tsx
│       └── SettingsScreen.tsx
├── android/
├── ios/
└── __tests__/

Running on Android TV / Fire TV (via ADB)

  1. Put the device in developer mode and enable ADB debugging (Settings → My Fire TV / Device → Developer options).
  2. Connect over the network:
    adb connect <device-ip>:5555
    adb devices   # confirm it shows up as "device", not "unauthorized"
  3. Make sure android/local.properties points at your SDK:
    sdk.dir=/path/to/Android/sdk
    
  4. Start Metro and deploy:
    npm start
    npm run android
    react-native run-android will install onto whichever device/emulator ADB currently sees — if you have more than one connected, pass --deviceId <id>.

Running on tvOS simulator

bundle install
bundle exec pod install --project-directory=ios
npm run ios -- --simulator="Apple TV"

Use xcrun simctl list devices to confirm the exact Apple TV simulator name installed on your machine if the default doesn't match.

Key learnings

Mobile development assumes a pointing device — touch — where the user can hit anything on screen directly. TV development assumes a 5-button remote, so every interactive element needs an explicit, predictable path into and out of focus; there's no "tap whatever you want" affordance, only "the engine guesses your next neighbor based on geometry, and you'd better make that guess correct." That shifts a meaningful share of the work from layout into focus management: deciding what's focusable, what's the initial focus target, and how focus should move when the implicit geometry-based guess is wrong. The other big shift is hardware ceiling — phones in this price range don't exist, but TV sticks at $30 do, so techniques that are "nice to have" on mobile (virtualized lists, native-driver animations, scoped re-renders) become load-bearing on TV. In short: mobile optimizes for direct manipulation, TV optimizes for directed, predictable navigation under a much lower performance budget.

About

Studying TV apps using react native

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors