Adding tvOS & Android TV Support to Monorepo
šŸ“ŗ

Adding tvOS & Android TV Support to Monorepo

Published
Published September 27, 2021
Author

TL;DR

I was in the process of creating a react-native monorepo when I stumbled uponĀ this awesome repoĀ thatĀ Matteo MazzaroloĀ is developing, so I decided to build off of that. His repo already supports iOS, Android, macOS, Windows, general desktop using Electron, web, and a browser extension, so I figured let's round it out with some TV support.
notion image

Initial Setup

First we will clone and setup the monorepo.
% git clone git@github.com:thefinnomenon/react-native-universal-monorepo.git % cd react-native-universal-monorepo % git checkout 06aafb1202c55f1a5d6625a54947334936558b33 % yarn

Add Scripts to Root

Now we can add some scripts for interacting with our TV project when we finish it.
// package.json { ... "scripts": { ... "tv:android:metro": "yarn workspace @my-app/tv start", "tv:android:start": "yarn workspace @my-app/tv android", "tv:android:studio": "yarn workspace @my-app/tv studio", "tv:tvos:metro": "yarn workspace @my-app/tv start", "tv:tvos:start": "yarn workspace @my-app/tv ios", "tv:tvos:xcode": "yarn workspace @my-app/tv xcode", "tv:tvos:pods": "yarn workspace @my-app/tv pods", ... } ... }

Initialize TV App

Let's useĀ react-native-template-typescript-tvĀ as a template to create our app.
% cd packages % npx react-native init tv --template react-native-template-typescript-tv % cd tv

Update package.json

Next rename the package to match the naming scheme of the project, add some helpful scripts, and add our app package and async-storage dependencies.
// packages/tv/package.json { "name": "@my-app/tv", ... "scripts": { ... "android": "emulator -avd Android_TV; react-native run-android", "ios": "react-native run-ios --simulator 'Apple TV' --scheme 'tv-tvOS'", "studio": "studio android", "xcode": "xed ios", "pods": "pod-install", ... }, "dependencies": { "@my-app/app": "*", "@react-native-async-storage/async-storage": "^1.15.7", ... }, ... }
% yarn add -D pod-install # Since aync-storage has a native component, we need to run this too % yarn pods
If you get an lstat error just rerun the command.

Modify App

Now we can replace the default App with the one from our app package.
// packages/tv/index.js import {AppRegistry} from 'react-native'; import {App} from '@my-app/app'; import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App);

Add Monorepo Support

If we run the app right now, it won't be able to resolve any of the hoisted dependencies so we have to configure it to work with the monorepo.
% yarn add -D react-native-monorepo-tools
If you get an lstat error just rerun the command.
// packages/tv/metro.config.js const exclusionList = require("metro-config/src/defaults/exclusionList"); const { getMetroTools, getMetroAndroidAssetsResolutionFix } = require("react-native-monorepo-tools"); const monorepoMetroTools = getMetroTools(); const androidAssetsResolutionFix = getMetroAndroidAssetsResolutionFix(); module.exports = { transformer: { // Apply the Android assets resolution fix to the public path... publicPath: androidAssetsResolutionFix.publicPath, getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: false, }, }), }, server: { // ...and to the server middleware. enhanceMiddleware: (middleware) => { return androidAssetsResolutionFix.applyMiddleware(middleware); }, }, // Add additional Yarn workspace package roots to the module map. // This allows importing importing from all the project's packages. watchFolders: monorepoMetroTools.watchFolders, resolver: { // Ensure we resolve nohoist libraries from this directory. blockList: exclusionList(monorepoMetroTools.blockList), extraNodeModules: monorepoMetroTools.extraNodeModules, }, };

Update config.ts for TV Sub-platforms

We have to update the platform detection code inĀ config.tsĀ to handle our new sub-platforms.
// packages/app/src/config.ts import { Platform } from 'react-native'; // Import TV specific types (uncomment if developing for TV platform) // import '../../tv/node_modules/react-native/tvos-types.d'; // eslint-disable-next-line declare var __SUBPLATFORM__: "electron" | "browser-ext" | "android-tv" | "tvos" | undefined; export const isDev = __DEV__; // Tells on what variant of the platform we're running export let subplatform: typeof __SUBPLATFORM__ = undefined; // Injected in electron and browser-extension builds. if (typeof __SUBPLATFORM__ === "string") { subplatform = __SUBPLATFORM__ } // For tvOS and Android TV, we can check the Platform.isTV field else if (Platform.isTV && Platform.OS === "ios") { subplatform = "tvos"; } else if (Platform.isTV && Platform.OS === "android") { subplatform = "android-tv"; }

Create Android TV Virtual Device

  1. Open SDK Manager and install theĀ Android TV Intel x86 Atom System ImageĀ for your current SDK.
  1. Open AVD Manager and create a TV device using the system image you just installed (Name itĀ Android TV).

Bump Android minSDKVersion

When I tried to build I got an error because the minSDKVersion was 16 and a library was requiring a minimum of 21 so we can bump it to 21.
// packages/tv/android/app/build.gradle ... android { ... defaultConfig { ... minSdkVersion 21 ... } ... } ...

Run

From root directory,
% yarn tv:tvos:start % yarn tv:android:start
You will see a warning, "Persistent storage is not supported on tvOS...". You can ignore this since we are simply using async-storage as an example, but if you want to actually persist data for a tvOS app you will have to find another solution.
notion image

Test Other Platforms

Now that we have our TV platforms working, we need to check to make sure the other platforms still work.
ForĀ web,Ā browser-ext, &Ā electronĀ I get this error,
The react-scripts package provided by Create React App requires a dependency: "jest": "26.6.0" Don't try to install it manually: your package manager does it automatically. However, a different version of jest was detected higher up in the tree: .../react-native-universal-monorepo/node_modules/jest (version: 26.6.3) Manually installing incompatible versions is known to cause hard-to-debug issues. If you would prefer to ignore this check, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project. That will permanently disable this message but you might encounter other issues.
This is because Jest is being hoisted but it shouldn't be an issue, so just follow the instructions to ignore the check by adding aĀ .envĀ file withĀ SKIP_PREFLIGHT_CHECK=trueĀ to each of those project directories.
ForĀ macosĀ I ran into a open file limit which I solved by installingĀ watchman
% brew update % brew install watchman
All the other platforms ran fine without modifications šŸ‘šŸ½.

Final Thoughts

It's really cool that react-native enables developers to write an app that runs on multiple platforms with minimal extra work. Having a monorepo with all the platforms already setup is a huge timesaver and now having TV supported, we can easily throw the new dancing cat app we have been working on up on the big screen so our family can pretend to be proud of us while they wonder why we couldn't have been a talented doctor like our cousin Greg.
Click to rocket boost to the top of the page!