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.
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
- Open SDK Manager and install theĀ
Android TV Intel x86 Atom System Image
Ā for your current SDK.
- 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.
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.