v2.55.0
This release adds first-class Meta Horizon OS support, a new ReactVision Studio integration layer, and a cross-reality JS routing component that lets a single app target phones, tablets and Meta Quest from one React surface. It also ships three Android / iOS stability fixes that unblock the latest Android ABI and resolve two regressions reported by the community.
Highlights
Meta Horizon OS support
VR scenes run natively on Meta Quest 3 / Quest Pro / Quest 2 / Quest 1 via a new OpenXR backend in virocore. The integration is end-to-end and has been device-validated at 90 Hz on Quest 3:
-
Dual-Activity architecture. The same APK ships an Android phone / tablet panel (
MainActivity) and an exclusive VR Activity (VRActivity, emitted automatically by the Expo plugin whenxRMode: ["QUEST"]is set). Consumers don't have to manage the switch — mount<ViroXRSceneNavigator>and entering/exiting VR is handled for you (see Cross-reality JS layer below). -
Two simultaneous pointers. Right and left controllers (or tracked hands) each get an independent cyan laser line, hover state, and click resolution. Either side can be controller-tracked or hand-tracked; right is the single-pointer fallback when only one is available.
-
Full input set. Touch controllers (triggers, grips, A/B/X/Y, menu, thumbsticks, haptics),
XR_EXT_hand_trackingjoints, andXR_FB_hand_tracking_aimfor fingertip-aimed pointing. Pinch-to-click and grip-to-grab are detected per hand. B and Menu buttons exit VR; returning from the Quest system menu relaunches automatically, explicitexitVRScene()calls don't. -
Stable hover and click on small UI targets. Pose hysteresis and a 75 ms hover grace window absorb natural OpenXR aim jitter, so hover state doesn't oscillate and trigger pulls land on first try.
-
Passthrough + recenter.
VRModuleOpenXR.setPassthroughEnabled(viewTag, …)andrecenterTracking(viewTag)for in-app control. -
Full lighting pipeline. HDR, PBR, bloom, shadows — all validated at 90 Hz with App=5–6 ms frame time on Quest 3.
-
Fast Refresh works inside VR. Metro Fast Refresh, timers, and
requestAnimationFramekeep working when the headset is on, just like in the 2D panel. -
questAppIdplugin option. Sideloaded builds no longer show the system "App Name Unavailable" overlay.
Cross-reality JS layer
Three additions to @reactvision/react-viro collapse the multi-platform story into a one-liner for downstream apps.
-
ViroXRSceneNavigator— auto-detects the platform and mounts the right navigator:ViroVRSceneNavigatoron Meta Quest,ViroARSceneNavigatoron iOS / non-Quest Android. Pass a single shared scene, or per-platform scenes:// Shared scene <ViroXRSceneNavigator initialScene={{ scene: MyScene }} /> // Per-platform scenes <ViroXRSceneNavigator arInitialScene={{ scene: MyARScene }} vrInitialScene={{ scene: MyVRScene }} />
-
Platform guards on the existing navigators.
ViroARSceneNavigatorshort-circuits with a clean fallback when mounted on Quest instead of trying to start an AR session;ViroVRSceneNavigatordoes the same on non-Quest Android instead of falling through to the deprecated Google Cardboard split-screen renderer. Both expose an override prop (questFallback/nonQuestFallback) for custom UIs (e.g. a "Launch VR" launcher button). -
isQuestandhasOpenXRSupport— runtime-detected booleans exported from the package root. Detection is hardware-ID based (Build.MANUFACTURER,BRAND,MODELviaPlatform.constants), not module-presence based, so a single APK that bundles Quest support does not misidentify regular phones as Quest.
Multi-pointer hooks
Two helpers for apps that want simple aggregated state without writing the per-source bookkeeping themselves.
import {
useAnySourceHover,
useAnySourcePressed,
} from "@reactvision/react-viro";
function MyButton() {
const [hovered, onHover] = useAnySourceHover();
const [pressed, onClickState] = useAnySourcePressed();
return (
<ViroNode onHover={onHover} onClickState={onClickState} onClick={fire}>
<ViroQuad
scale={pressed ? [0.95, 0.95, 0.95] : [1, 1, 1]}
materials={[hovered ? "btnHover" : "btnIdle"]}
/>
</ViroNode>
);
}Both hooks return [bool, handler] and deduplicate per source ID so a second pointer crossing an already-hovered node does not produce spurious enter/exit toggles in JS state. Apps that do care which specific pointer fired the event (drag-to-controller, single-handed gestures) read source directly from the raw callback.
StudioSceneNavigator
Drop-in component for ReactVision Studio scenes. Fetches a Studio-authored scene by UUID via rvGetScene(sceneId) — auth is the project's API key, wired through the Expo plugin (rvProjectId in app.json):
import { StudioSceneNavigator } from "@reactvision/react-viro";
<StudioSceneNavigator sceneId="abc-123-uuid" style={StyleSheet.absoluteFill} />;What it renders, end to end:
- 3D models (GLB / VRX), images, video, and text from the scene's asset registry
- Per-asset placement (position / rotation / scale) and physics bodies
- Scene functions:
NAVIGATION(push to another scene),ALERT,ANIMATION - Collision bindings (asset-pair triggered functions)
- Animation registry (Studio-authored keyframes pushed into
ViroAnimations) - Material configs with optional shader modifiers — animated
timeuniform for animated presets,_rf_vpw/_rf_vphviewport uniforms for screen-space effects, and auto-flaggedrequiresCameraTexturewhen the shader samples the camera feed - Image-tracking targets and the
Viro360Image/Viro360Videobackground - Physics world configs
StudioSceneNavigator is fully cross-reality on Quest — the same sceneId mounts the scene in AR on phones / tablets and in VR on headset, with no consumer-side branching.
Underlying API surface
StudioSceneNavigator is powered by two new endpoints in the ReactVision Cloud Anchor SDK (libreactvisioncca, shipped inside virocore):
getScene(sceneId, callback)— returns the full scene response (metadata, assets, animations, collision bindings, scene functions, project info).getSceneAssets(sceneId, callback)— asset-list-only variant for clients that already have scene metadata cached.
Both are exposed to JS via ViroARSceneNavigator.rvGetScene(sceneId) and rvGetSceneAssets(sceneId), and authenticate with the project API key wired through the Expo plugin. Existing Cloud Anchor / Geospatial endpoints are unchanged.
Fixes
-
16 KB
.sopage alignment forlibvrapi.so(Issue A). The Android 2025 ABI requires all shipped.sofiles to align to 16 KB pages.libvrapi.sois now repackaged with-Wl,-z,max-page-size=16384, resolving load failures on devices with the new page size. -
ViroARImageMarkerchildren fixed-on-screen after re-detection (Android, GitHub #465). Models parented to an image marker no longer pin to screen coordinates after the target was lost and re-acquired in v2.54.0. Markers re-anchor cleanly to the detected world pose every time, including subsequent re-detection. -
iOS
ViroPortalSceneportal-tree stability (GitHub #452). Continued portal-render-pass hardening on top of the v2.54.0 fix:- Portal stencil silhouette no longer drops transparent entry fragments before alpha discard runs.
- 360° background inside a portal is no longer overwritten by the AR camera background drawn afterwards.
- The interior of a portal hole no longer reveals the portal interior when the user is outside a nested exit-frame portal.
- AR occlusion is disabled inside the portal interior so virtual content is no longer discarded by depth-based occlusion when nested.
Compatibility
- React Native 0.83, Expo 54 + Expo 55 for AR / non-Quest paths (unchanged from 2.54.0).
- Android Gradle Plugin 8.7+ is required for 16 KB-aligned APKs. Minimum SDK unchanged (24).
- Meta Quest support requires React Native ≥ 0.83 / Expo ≥ 55.
ViroXRSceneNavigatorthrows an actionable JS error if the runtime is below the threshold. AR continues to work on the Expo 54 + RN 0.79 baseline. - Meta Quest support also requires the Quest variant of the Viro Android package —
ReactViroPackage(ReactViroPackage.ViroPlatform.QUEST)inMainApplication.kt, plusxRMode: ["QUEST"]in the Expo plugin config. Both are emitted automatically byexpo prebuildwhen the plugin is configured. - Recommended install path: Expo Dev Client. Bare React Native is not tested for this release. It should work but will need a substantial amount of manual wiring —
MainApplication.ktpackage registration, theVRActivityAndroid Activity declaration with the correct intent filter and Quest hardware features, thexRModeQuest manifest features, and iOS Podfile configuration. The Expo plugin generates all of these automatically. Bare RN support will be revisited in a follow-up release. - iOS and non-Quest Android code paths are unchanged. Existing apps upgrade with no code changes.
Migration
No breaking JS API changes. Existing ViroARSceneNavigator and ViroVRSceneNavigator usage continues to work. New cross-reality apps should mount <ViroXRSceneNavigator> and wire its onExitViro prop to take the panel back to wherever VR was launched from (e.g. navigation.goBack()). B / Menu buttons exit VR automatically; exitVRScene() from @reactvision/react-viro is only needed for programmatic in-scene exits.