@symbiote-native/screen-orientation wraps
expo-screen-orientation —
orientation locking, reading the current orientation and lock, and an auto-updating
orientation-change subscription — so every SymbioteNative adapter can reach it, not just React.
Like network, it mixes stateless one-shot functions
(lockAsync/unlockAsync/getOrientationAsync/…) with exactly one live subscription:
useScreenOrientation seeds from one-shot getOrientationAsync()/getOrientationLockAsync()
calls and then subscribes to addOrientationChangeListener for live updates, the same
seed-then-subscribe shape as network’s own useNetworkState.
expo-screen-orientation and expo-modules-core come along as regular dependencies, pinned to
exact versions — never install either yourself, and never add the expo meta-package to your
project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).
No platform permission string is needed — locking and reading the current orientation read/write
system state with no runtime permission prompt on either platform.
Gets the current orientation lock, falling back to the last value set via lockAsync()/lockPlatformAsync() when the native method itself isn’t available
Any landscape orientation. WebOrientationLock only
ANY
'any'
Any orientation. WebOrientationLock only
NATURAL
'natural'
The device’s natural orientation. WebOrientationLock only
UNKNOWN
'unknown'
The orientation/lock could not be determined
WebOrientation only defines the four primary/secondary members (no PORTRAIT/LANDSCAPE/
ANY/NATURAL) — it describes a resolved orientation, not a lock policy.
iOS rejects a lock the device cannot satisfy; Android accepts it.lockAsync and
lockPlatformAsync check the requested mask against isSupportedByDevice() and throw
UnsupportedOrientationLockException otherwise — that is how PORTRAIT_DOWN (and ALL, which
contains it) fails on an iPhone. Android writes the value straight to
activity.requestedOrientation, and only a lock it cannot map raises at all. Probe with
supportsOrientationLockAsync before locking if you care about the difference.
On Android the lock belongs to the current activity. Every native function resolves
appContext.currentActivity and throws MissingActivity when there is none, and the module
writes the activity’s initial requestedOrientation back when it is destroyed — a lock does not
outlive the activity that set it.
iOS re-applies the last lock when the app returns to the foreground. The module unregisters
itself while backgrounded and re-asserts the last mask it set on resume; the Android path has no
equivalent step.
getOrientationAsync on Android is best effort. It derives the orientation from the display
rotation plus window metrics rather than from a dedicated system API — upstream’s own comment
says surface rotation is not standardized across devices. Devices whose natural orientation is
landscape take a different rotation-to-orientation branch, so a tablet and a phone held at the
same physical angle can report different values.
@symbiote-native/screen-orientation ships zero React/Vue/Angular logic in
expo-screen-orientation itself — its functions and types are hand-ported, verbatim, into this
package’s own core/, resolving the native module through expo-modules-core’s
requireNativeModule rather than the expo meta-package this project never installs:
Each adapter’s hook/composable/rune/service is a thin lifecycle wrapper — seed from one-shot
getOrientationAsync()/getOrientationLockAsync() calls, subscribe to
addOrientationChangeListener, unsubscribe on unmount — over the same core functions, the same
one-listener seed-then-subscribe shape as network’s
useNetworkState/NetworkStateService. The native code itself is never vendored or copied —
expo-modules-autolinking resolves it straight out of node_modules (see the native setup
guide).