Skip to content

fix: Android TV press event support (onPress, onLongPress, onPressIn, onPressOut)#3980

Open
JavanPoirier wants to merge 1 commit into
tamagui:mainfrom
JavanPoirier:fix-tv-press-events
Open

fix: Android TV press event support (onPress, onLongPress, onPressIn, onPressOut)#3980
JavanPoirier wants to merge 1 commit into
tamagui:mainfrom
JavanPoirier:fix-tv-press-events

Conversation

@JavanPoirier

@JavanPoirier JavanPoirier commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Problem

On Android TV with the New Architecture (Fabric), none of onPress, onLongPress, onPressIn, or onPressOut were firing on Tamagui components when navigating with the D-pad remote. Replaces PR: #3970, Fixes #3957

Root Causes

1. RNGH was intercepting TV remote events

isUsingRNGH was true on TV because it only checked gh.isEnabled. RNGH's GestureDetector wraps the view in its own native gesture recogniser which does not translate TV remote select-button events into press callbacks — it only handles touch/pointer events.

Fix: isUsingRNGH = gh.isEnabled && !Platform.isTV. TV builds take the useMainThreadPressEvents path instead.


2. Android TV Fabric: onPress and onLongPress are not valid props

The Android TV TVViewConfig Fabric codegen spec only declares a subset of props. onPress and onLongPress are not in that spec. Passing them causes Fabric to call setter.apply(node, val) where setter is undefined → crash or silent no-op.

The select button fires onClick (not onPress) and long-hold fires onLongClick on Android TV Fabric.

tvOS is unaffected — onPress and onLongPress are valid props there.

Fix in mainThreadPressEvents.native.ts:

if (Platform.OS === 'android') {
  viewProps.onClick    = events.onPress
  viewProps.onLongClick = events.onLongPress
} else {
  viewProps.onPress    = events.onPress
  viewProps.onLongPress = events.onLongPress
}

onPressIn and onPressOut are valid in both Android TV and tvOS Fabric specs.


3. Responder handlers crashing Android TV Fabric

useMainThreadPressEvents sets responder handlers (onStartShouldSetResponder, onResponderGrant, etc.) that are also not in the Android TV Fabric spec. These were already being deleted in eventHandling.native.ts via androidTVFabricIncompatibleHandlers, but a subtle bug meant isPressEnabled could be truthy when events was null, causing the cleanup not to run in time.

Fix:

const isPressEnabled = events != null ? hasPressEvents : false

4. tamagui-build: native imports not resolving to .native.js in local builds

Why does the released NPM version work but not a local build?

tamagui-build uses babel-plugin-fully-specified to rewrite bare extension-less imports (e.g. './helpers/mainThreadPressEvents') to fully-specified paths (e.g. './helpers/mainThreadPressEvents.native.js') for native builds.

The plugin is configured with tryExtensions — a list of file extensions to check on disk to verify the target exists before rewriting the import. The previous configuration only had tryExtensions: ['.js'] for native builds.

The failure scenario:

When tamagui-build runs the native build pass, it compiles only .native.ts files. At the point babel runs on those compiled outputs, mainThreadPressEvents.js (the web stub) does not yet exist on disk — the web build pass hasn't run yet. The plugin checks for .js, finds nothing, and silently skips rewriting the import.

The import stays as bare './helpers/mainThreadPressEvents'. Metro then resolves it using its own platform extension rules. In most cases Metro correctly prefers .native.js, but in some environments (custom resolvers, Expo prebuild, or when symlinked from a monorepo workspace) the bare import resolves to the web stub instead — a no-op that exports an empty function.

The released NPM version works because the published package is built with web and native passes running sequentially in a controlled CI environment. By the time the native babel pass runs, the web build's mainThreadPressEvents.js already exists on disk, the plugin finds it under tryExtensions: ['.js'], and correctly rewrites to .native.js. Local developer builds using bun run build or bun run watch do not have this ordering guarantee.

Fix:

tryExtensions: platform === 'native' ? ['.native.js', '.js'] : ['.js'],

By checking .native.js first, the plugin finds mainThreadPressEvents.native.js immediately (it was just compiled in this same pass) and rewrites the import correctly — regardless of whether the web stub has been built yet.


5. setupMatchMedia calling with invalid argument on TV

setupMatchMedia could be called with a non-function argument (e.g. from third-party integrations). Previously this only logged in dev — in production it would still attempt to assign the invalid value as the matchMedia implementation, causing window.matchMedia is not a function errors later.

Fix: Added an unconditional return early exit when _ is not a function, and moved the dev warning inside that branch. Also assigns the implementation to globalThis['matchMedia'] so third-party code that calls window.matchMedia directly (bypassing Tamagui's re-exported helper) gets the correct implementation.


Files Changed

File Change
code/core/web/src/eventHandling.native.ts Bypass RNGH on TV; fix isPressEnabled null guard; add Platform import
code/core/web/src/helpers/mainThreadPressEvents.native.ts Map onPressonClick and onLongPressonLongClick on Android TV; keep onPress/onLongPress on tvOS
code/core/web/src/helpers/matchMedia.native.ts Guard against non-function arg; assign to globalThis.matchMedia
code/packages/build/tamagui-build.js Add tryExtensions: ['.native.js', '.js'] for native builds

Fixes Issue #3957

{
esExtensionDefault: platform === 'native' ? '.native.js' : '.mjs',
esExtensions: platform === 'native' ? ['.js'] : ['.mjs'],
tryExtensions: platform === 'native' ? ['.native.js', '.js'] : ['.js'],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check this pls. Without it, my local build would fail to run on Android TV.

@JavanPoirier

Copy link
Copy Markdown
Contributor Author

@natew, this PR was updated from your comments in #3970. Would love to get this in. Thanks

@natew

natew commented Jun 9, 2026

Copy link
Copy Markdown
Member

Sorry this has been taking so long. v2 is finally out so I can get onto it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[V2] Button onPress not firing on android TV (V1 regression)

2 participants