expo-rotation-module
v1.0.3
Published
Screen Orientation Native Module
Maintainers
Readme
expo-rotation-module
A small Expo native module that lets an Android app read and control the system rotation settings (ACCELEROMETER_ROTATION and USER_ROTATION) and request the WRITE_SETTINGS permission.
This module exposes a tiny JS/TS API and includes Android and iOS native source. The runtime behavior is Android-only; iOS/web are no-ops.
Usage
Install
From your app project (local development):
pnpm add ../path/to/expo-rotation-module(or use a published package:pnpm add expo-rotation-module)
IMPORTANT: add the config plugin to your app config so the permission is injected during
prebuild.In
app.jsonorapp.config.jsadd the package name toexpo.plugins:{ "expo": { "plugins": [ "expo-rotation-module" ] } }- The plugin is implemented in
plugin/index.jsand will insert theWRITE_SETTINGSpermission into yourAndroidManifest.xmlduringexpo prebuild.
- The plugin is implemented in
Apply native changes and build the app:
npx expo prebuild(orexpo run:androidwhich runs prebuild automatically)- Rebuild/install the app on device/emulator (use a custom dev client or a standalone build for WRITE_SETTINGS tests — Expo Go is not suitable).
API
Import the module in JS/TS:
import Rotation, {
canWrite,
requestWritePermission,
getRotationState,
setRotationState,
getPackageName,
} from 'expo-rotation-module';canWrite(): Promise<boolean>— returns true if the app hasWRITE_SETTINGSgranted (Android M+). Returnstrueon older OS versions.requestWritePermission(): void— opens the Android Settings screen where the user can grant the permission for your app.getRotationState(): Promise<'AUTOROTATE'|'PORTRAIT'|'LANDSCAPE'>— reads the current global rotation state. The module reports the coarse axis (PORTRAIT or LANDSCAPE); within those axes the system sensor still allows regular or inverted orientations when auto-rotate is off.setRotationState(state): Promise<void>— sets the rotation state. Rejects with an Error that may contain acodeproperty (e.g.E_PERMISSION).getPackageName(): Promise<string>— returns the package name the native module is using (useful for diagnostics).
All functions are Android-only. On non-Android platforms the functions are no-ops or return safe defaults.
Example
import Rotation from 'expo-rotation-module';
async function ensureAndSet() {
const hasPermission = await Rotation.canWrite();
if (!hasPermission) {
// Opens Settings where the user can grant WRITE_SETTINGS for your app
Rotation.requestWritePermission();
return;
}
await Rotation.setRotationState('PORTRAIT');
}Troubleshooting
WRITE_SETTINGS switch is greyed out in the Settings screen:
- Ensure your app has the
WRITE_SETTINGSpermission declared in the manifest. The config plugin adds this when you runexpo prebuild. - Do not test this inside Expo Go — the Settings screen will target the Expo Go package. Build a custom dev client or standalone build.
- Confirm the package name by calling
Rotation.getPackageName()and check device logs for theOpening WRITE_SETTINGS for package: <pkg>message.
- Ensure your app has the
ESLint/type errors in consuming projects:
- Ensure your app can resolve the package. For local linking with pnpm workspaces,
pnpm installin the app and a restart of the editor/TS server is sometimes required.
- Ensure your app can resolve the package. For local linking with pnpm workspaces,
Development
This section explains how to develop, test and publish this module.
Repo layout (important files)
src/— TypeScript JS wrapper and types.android/— Android native sources (Kotlin) and Gradle files.ios/— iOS native sources (Swift) and podspec.plugin/— config plugin that injects theWRITE_SETTINGSpermission.app.plugin.js— plugin entry point for Expo to discover the config plugin.index.js— package entry that re-exports the JS wrapper.package.json— package metadata, scripts and publish config.
Local development
Install dependencies in the module repo:
pnpm install
Make changes to
src/or native code.Build/prepare artifacts (some scripts expect
tsc/expo-module-scripts):pnpm run prepare(runsexpo-module prepare) orpnpm run build(if you prefer).
Test in an app:
- Create or use an example app. From the app root:
- Add the local package:
pnpm add ../path/to/expo-rotation-module(orpnpm add file:...) - Ensure
app.jsonincludes the plugin (see Usage). - Run
npx expo prebuildand verifyandroid/app/src/main/AndroidManifest.xmlcontains<uses-permission android:name="android.permission.WRITE_SETTINGS" />. - Build and run the app on device/emulator (
expo run:androidor a dev client build).
- Add the local package:
- Create or use an example app. From the app root:
Faster iteration (optional):
- Instead of building the package, map Metro to the module
srcin the appmetro.config.jsduring development:// example: app/metro.config.js const path = require('path'); module.exports = { resolver: { extraNodeModules: { 'expo-rotation-module': path.resolve(__dirname, '../path/to/expo-rotation-module/src'), }, }, watchFolders: [path.resolve(__dirname, '..')], }; - This lets the app load the TypeScript source directly without rebuilding the package on every change.
- Instead of building the package, map Metro to the module
Publishing
We publish the source package (native sources included). Before publishing, ensure:
package.jsonfields are correct (name,version,main,types,files).devDependenciescontainstypescriptsoexpo-module prepareruns in CI.- The plugin and native sources are included in the published package (use the
filesarray inpackage.jsonto control this).
Manual publish (local):
Prepare the package locally:
pnpm installpnpm run prepare
Create a tarball to test what will be published:
npm pack- Install the tarball into a test app:
pnpm add ../expo-rotation-module-<version>.tgz - Run
npx expo prebuildin the test app to confirm the plugin and native sources are applied.
Publish:
- If your environment has an npm automation token configured in CI, run
npm publish --access publicfrom the package root. - If
prepublishOnlyscripts fail in your environment, you can publish with scripts ignored after preparing locally:npm publish --access public --ignore-scripts
- If your environment has an npm automation token configured in CI, run
Continuous Integration / GitHub Actions
Recommended CI flow:
pnpm install --frozen-lockfilepnpm run prepare(ensuretypescriptis installed indevDependenciessotscis available)- Run tests/lint (optional)
- Bump version, push tags
- Authenticate to npm (use an automation token or GitHub OIDC if your npm org supports trusted publishers)
npm publish --access publicorpnpm publish --access public
Example workflow (summary):
- Checkout, setup Node + pnpm,
pnpm install, runpnpm run prepare, bump version, publish.
- Checkout, setup Node + pnpm,
If you find anything missing or unclear (examples, API signatures, or CI details) open an issue or submit a PR. Contributions welcome.
