expo: [expo-screen-orientation] Screen orientation does not lock or trigger with EAS Build

Summary

On a built app with EAS build, calling https://docs.expo.dev/versions/latest/sdk/screen-orientation/#screenorientationlockasyncorientationlock does not trigger the screen to rotate and lock on iOS. When the app isn’t built with EAS build, and created with expo build, the screen orientation lock async function works.

Could possibly be related to: https://github.com/expo/expo/issues/11558

Managed or bare workflow? If you have ios/ or android/ directories in your project, the answer is bare!

managed

What platform(s) does this occur on?

iOS

SDK Version (managed workflow only)

43

Environment

Expo CLI 4.12.10 environment info: System: OS: macOS 11.6 Shell: 5.8 - /bin/zsh Binaries: Node: 14.15.1 - ~/.nvm/versions/node/v14.15.1/bin/node Yarn: 3.0.2 - ~/.nvm/versions/node/v14.15.1/bin/yarn npm: 6.14.8 - ~/.nvm/versions/node/v14.15.1/bin/npm Watchman: 2021.06.07.00 - /usr/local/bin/watchman Managers: CocoaPods: 1.11.2 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: iOS 14.5, DriverKit 20.4, macOS 11.3, tvOS 14.5, watchOS 7.4 Android SDK: API Levels: 30, 31 Build Tools: 29.0.2, 30.0.2, 31.0.0 System Images: android-30 | Google APIs Intel x86 Atom, android-30 | Google Play Intel x86 Atom, android-31 | Android TV Intel x86 Atom, android-31 | ARM 64 v8a, android-31 | Intel x86 Atom_64, android-31 | Google TV Intel x86 Atom, android-31 | Google APIs ARM 64 v8a, android-31 | Google APIs Intel x86 Atom_64, android-31 | Google Play ARM 64 v8a, android-31 | Google Play Intel x86 Atom_64 IDEs: Android Studio: 4.1 AI-201.8743.12.41.6953283 Xcode: 12.5.1/12E507 - /usr/bin/xcodebuild npmPackages: expo: ^43.0.0 => 43.0.1 react: 17.0.1 => 17.0.1 react-dom: 17.0.1 => 17.0.1 react-native: 0.64.2 => 0.64.2 react-native-web: 0.17.1 => 0.17.1 npmGlobalPackages: expo-cli: 4.12.10 Expo Workflow: bare

Reproducible demo or steps to reproduce from a blank project

  1. Create a build with EAS Build for iOS
  2. Call the function https://docs.expo.dev/versions/latest/sdk/screen-orientation/#screenorientationlockasyncorientationlock
  3. Foregrounding/backgrounding the app and calling the function again does not trigger the screen to lock orientation.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 33
  • Comments: 92 (9 by maintainers)

Most upvoted comments

Experiencing the same issue. Used to work before transitioning to eas build, but now calling ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT) does nothing.

also using EAS and experiencing this behavior.

i can call ScreenOrientation.unlockAsync() and then ScreenOrientation.getPlatformOrientationLockAsync() will return

{
  screenOrientationArrayIOS: [
    1, // Orientation.PORTRAIT_UP
    3, // Orientation.LANDSCAPE_LEFT
    4, // Orientation.LANDSCAPE_RIGHT
  ]
}

however, the screen will not rotate when tilting the phone – made sure portrait lock was turned off on the phone too.

calling ScreenOrientation.lockAsync(OrientationLock.LANDSCAPE_RIGHT) does not have any effect either, but no errors are thrown.

app.json does have "orientation": "portrait"; however, the expo-screen-orientation docs say it will override this and, at least from what it’s returning in the getPlatformOrientationLockAsync, it does seem to think it’s overriding it.

edit: also did set expo.ios.requireFullScreen = true as the expo-screen-orientation docs say to do.

Any update on this ? I understand that this is because the EAS uses a standalone app that runs on Android and iOS devices, and it doesn’t support some of the modules that are available in the standard Expo runtime.

Is there a workaround for this issue, or an alternative method that can be used to lock the screen orientation in an EAS project?

 "expo": "~46.0.16",
 "react-native": "0.69.6",
 "react-native-screens": "~3.15.0"
 "expo-screen-orientation": "~4.3.0",```

Also experiencing this issue on SDK 44 (EAS build) with no reliable workaround.

I see. Could be viable workaround to remove "orientation": "portrait" from app.json and then do ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) immediately after app start (e.g. in an effect in App.tsx). I will test this and share my findings.

For me, adding expo-sensors as a dependency solved the issues I was having with this library

Don’t forget to make a new development build after adding the new package

I also deleted the “orientation” param from app.json and instead configured the plugin like so:

    "plugins": [
      [
        "expo-screen-orientation",
        {
          "initialOrientation": "DEFAULT"
        }
      ],
    ],

I might be narrowing down the issue. It might be iOS 16 + using a modal presented view in ReactNavigation. I’ll see if I can create a minimal reproducible.

Can confirm that ScreenOrientation.lockAsync simply doesn’t do anything on iOS 16. No problems on Android or iOS <=15. The problem also occurs in the Expo Go app on iOS 16. My assumption is that @brycnguyen still uses iOS 15 in his iOS Simulator and therefor thought this problem only occurs in EAS Build apps. Can you confirm/decline @brycnguyen and update this issue’s title if this is the case?

facing the same issue in 44

Still seeing this issue in Expo SDK44. Did an upgrade from 42 -> 43 -> 44. Readded Expo updates and still do not have any orientation working.

@watchinharrison exactly.

I removed "orientation": "portrait" from app.json and lock the orientation on app start using ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP). Then later I unlock/relock again using ScreenOrientation as needed.

@AntonioGally This hook looks really nice! By the way, one thing I found after finishing my implementation is that positive and negative values for X and Y are flipped between Android and iOS (my implementation is correct for iOS devices).

I wanted to disable portrait mode on iPads and the following configuration worked for my case after trying lots of other options:

    // delete "orientation" property
    "ios": {
      "supportsTablet": true,
      "requireFullScreen": true,
      "bundleIdentifier": "com.epts.ActinMobile",
      "infoPlist": {
        "UISupportedInterfaceOrientations": [
          "UIInterfaceOrientationLandscapeRight",
          "UIInterfaceOrientationLandscapeLeft"
        ],
        "UISupportedInterfaceOrientations~ipad": [
          "UIInterfaceOrientationLandscapeRight",
          "UIInterfaceOrientationLandscapeLeft"
        ]
      }
    },

Same issue here in 44…

Same issue here in 44.

I’m facing similar problem with Expo 47, but there I have a little twist - if I do:

await ScreenOrientation.lockAsync(
  ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT
);
await ScreenOrientation.lockAsync(
  ScreenOrientation.OrientationLock.DEFAULT
);

then it seems to reset screen orientation lock and from here on everything works as intended, but if I do just

await ScreenOrientation.lockAsync(
  ScreenOrientation.OrientationLock.DEFAULT
);

then it does not. Problem is - locking and unlocking orientation like this results in ugly layout shifts which my client would never accept. I also tried:

await ScreenOrientation.lockAsync(
  ScreenOrientation.OrientationLock.PORTRAIT// also _UP and _DOWN
);
await ScreenOrientation.lockAsync(
  ScreenOrientation.OrientationLock.DEFAULT
);

but doing it like so won’t reset orientation locking.

EDIT:

I managed to find an interesting workaround for this issue, it looks like this:

import { Dimensions } from "react-native";
import * as ScreenOrientation from "expo-screen-orientation";

function getIsLandscape(screen) {
  return screen.width > screen.height;
}

async handleDimensionsChange(newDimensions) {
  const isLandscape = getIsLandscape(newDimensions.screen);

  if (isLandscape) {
    await ScreenOrientation.lockAsync(
      ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT
    );
  } else {
    await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
  }
}

Dimensions.addEventListener(
  "change",
  this.handleDimensionsChange
);

Since Dimensions report orientation change just fine (they get updated on orientation change with or without lock), I was able to use it for orientation change detection and use screen width/height ratio to determine how should I lock orientation.

It works nice, though keep in mind that by using it you limit your app to use only one landscape variant.

EDIT 2:

Above fix seemed nice, but it only worked on dev client, turns out this doesn’t work in standalone build. I ended up implementing my own screen orientation handler using Accelerometer, it looks like this:

import React from "react";
import * as ScreenOrientation from "expo-screen-orientation";
import { Accelerometer } from "expo-sensors";
import autoBind from "auto-bind";

class ScreenOrientationSetter extends React.Component {
  state = {
    orientation: ScreenOrientation.OrientationLock.PORTRAIT_UP
  };

  constructor(props) {
    super(props);

    autoBind.react(this);
  }

  async handleAccelerometerData(data) {
    let newOrientation = this.state.orientation;
    
    // Thresholds here are set arbitrarily, 
    // but after fiddling with it a little I found them to be quite nice

    if (data.y < -0.8) {
      newOrientation = ScreenOrientation.OrientationLock.PORTRAIT_UP;
    } else if (data.x < -0.8) {
      newOrientation = ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT;
    } else if (data.x > 0.8) {
      newOrientation = ScreenOrientation.OrientationLock.LANDSCAPE_LEFT;
    }

    if (newOrientation !== this.state.orientation) {
      await ScreenOrientation.lockAsync(newOrientation);
      this.setState({ orientation: newOrientation });
    }
  }

  async componentDidMount(): void {
    this.accelerometerListener = Accelerometer.addListener(
      this.handleAccelerometerData
    );
    Accelerometer.setUpdateInterval(1000);
  }

  componentWillUnmount(): void {
    this.accelerometerListener.remove();
  }

  render() {
    // ...
  }
}

Same request as for @Ahmed-Imam3 [so far it seems like there’s no solution yet?]

expo 46, still have this issue

Still an issue for us as of August 21. Have not found a reliable workaround

Edit: Expo 45

I’m running into the same issue on my iPad with a build with EAS, “expo-screen-orientation” as a dependency, and SDK45, this is the output:

image

On the one hand, the orientation does not respect my app.config definition since the expo-screen-orientation plugin produces modifications on the info.plist so I put device-specific rules to override the values default

(As seen in the screenshot) it seems that the react-navigation for some reason uses the portrait mode (does this have something to do with EXDefaultScreenOrientationMask coming from the plugin?), it also seems that the application is taking landscape mode correctly (but it’s not using my device-specific definition since it’s fixed to UIInterfaceOrientationLandscapeRight)

on the other hand, if I remove the expo-screen-orientation dependency, everything works fine,

I’ve been trying for days to find the perfect combination so that everything works perfectly, but I can’t get it,

I attach the output of expo config --type introspect in case it is useful:

Click to expand!
{
  name: 'Ámbito Dólar',
  slug: 'ambito-dolar',
  privacy: 'hidden',
  version: '6.0.0',
  platforms: [
    'android',
    'ios',
    'web'
  ],
  userInterfaceStyle: 'automatic',
  icon: './assets/icon.png',
  assetBundlePatterns: [
    '**/*'
  ],
  plugins: [
    'sentry-expo',
    [
      '@config-plugins/react-native-quick-actions',
      [
        {
          title: 'Conversor',
          type: 'Conversion',
          iconType: 'UIApplicationShortcutIconTypeCompose'
        }
      ]
    ],
    'expo-screen-orientation'
  ],
  jsEngine: 'hermes',
  description: undefined,
  sdkVersion: '45.0.0',
  notification: {
    icon: './assets/icon.notification.png',
    color: '#00AE6B'
  },
  extra: {
    registerDeviceUri: '...',
    ratesUri: '...',
    historicalRatesUri: '...',
    sentryUri: '...',
    amplitudeKey: '...',
    firebaseConfigJson: '...',
    experienceId: '...'
  },
  updates: {
    fallbackToCacheTimeout: 0
  },
  splash: {
    image: './assets/splash-dark-content.png',
    backgroundColor: '#FFFFFF'
  },
  ios: {
    bundleIdentifier: 'im.outa.AmbitoDolar',
    buildNumber: '65',
    appStoreUrl: 'https://apps.apple.com/app/id1485120819',
    supportsTablet: true,
    requireFullScreen: true,
    config: {
      usesNonExemptEncryption: false
    },
    infoPlist: {
      CFBundleDevelopmentRegion: 'es',
      CFBundleExecutable: '$(EXECUTABLE_NAME)',
      CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)',
      CFBundleName: '$(PRODUCT_NAME)',
      CFBundlePackageType: '$(PRODUCT_BUNDLE_PACKAGE_TYPE)',
      CFBundleInfoDictionaryVersion: '6.0',
      CFBundleSignature: '????',
      LSRequiresIPhoneOS: true,
      UILaunchStoryboardName: 'SplashScreen',
      UIRequiredDeviceCapabilities: [
        'armv7'
      ],
      UIViewControllerBasedStatusBarAppearance: false,
      UIStatusBarStyle: 'UIStatusBarStyleDefault',
      'UISupportedInterfaceOrientations~iphone': [
        'UIInterfaceOrientationPortrait'
      ],
      'UISupportedInterfaceOrientations~ipad': [
        'UIInterfaceOrientationLandscapeLeft',
        'UIInterfaceOrientationLandscapeRight'
      ],
      LSApplicationQueriesSchemes: [
        'tweetbot',
        'twitter',
        'tg',
        'instagram',
        'fb',
        'apollo',
        'reddit',
        'com.hammerandchisel.discord',
        'slack',
        'github'
      ],
      CFBundleShortVersionString: '6.0.0',
      CFBundleVersion: '65',
      ITSAppUsesNonExemptEncryption: false,
      CFBundleURLTypes: [
        {
          CFBundleURLSchemes: [
            'im.outa.AmbitoDolar'
          ]
        },
        {
          CFBundleURLSchemes: [
            'exp+ambito-dolar'
          ]
        }
      ],
      UIRequiresFullScreen: true,
      UISupportedInterfaceOrientations: [
        'UIInterfaceOrientationPortrait',
        'UIInterfaceOrientationPortraitUpsideDown',
        'UIInterfaceOrientationLandscapeLeft',
        'UIInterfaceOrientationLandscapeRight'
      ],
      CFBundleDisplayName: 'Ámbito Dólar',
      UIUserInterfaceStyle: 'Automatic',
      UIApplicationShortcutItems: [
        {
          UIApplicationShortcutItemIconType: 'UIApplicationShortcutIconTypeCompose',
          UIApplicationShortcutItemTitle: 'Conversor',
          UIApplicationShortcutItemType: 'Conversion'
        }
      ],
      NSAppTransportSecurity: {
        NSAllowsArbitraryLoads: true,
        NSExceptionDomains: {
          localhost: {
            NSExceptionAllowsInsecureHTTPLoads: true
          }
        }
      }
    },
    splash: {
      image: './assets/splash-dark-content.png',
      backgroundColor: '#FFFFFF',
      dark: {
        image: './assets/splash-soft-white-content.png',
        backgroundColor: '#000000'
      }
    },
    entitlements: {
      'aps-environment': 'development'
    }
  },
  android: {
    package: 'im.outa.AmbitoDolar',
    versionCode: 65,
    icon: './assets/icon.android.png',
    playStoreUrl: 'https://play.google.com/store/apps/details?id=im.outa.AmbitoDolar',
    permissions: [
      'android.permission.READ_EXTERNAL_STORAGE',
      'android.permission.WRITE_EXTERNAL_STORAGE',
      'android.permission.INTERNET'
    ],
    googleServicesFile: './google-services.json',
    allowBackup: false,
    softwareKeyboardLayoutMode: 'pan',
    adaptiveIcon: {
      foregroundImage: './assets/icon.android.adaptive.foreground.png',
      backgroundImage: './assets/icon.android.adaptive.background.png'
    },
    splash: {
      image: './assets/splash-dark-content.png',
      backgroundColor: '#FFFFFF',
      dark: {
        image: './assets/splash-soft-white-content.png',
        backgroundColor: '#000000'
      }
    }
  },
  hooks: {
    postPublish: [
      {
        file: 'sentry-expo/upload-sourcemaps',
        config: {
          organization: '...',
          project: 'ambito-dolar',
          authToken: '...'
        }
      }
    ]
  },
  _internal: {
    isDebug: false,
    projectRoot: '/Users/outaTiME/Code/ambito-dolar/packages/client',
    dynamicConfigPath: '/Users/outaTiME/Code/ambito-dolar/packages/client/app.config.js',
    packageJsonPath: '/Users/outaTiME/Code/ambito-dolar/packages/client/package.json',
    autolinkedModules: [
      'expo',
      'expo-analytics-amplitude',
      'expo-application',
      'expo-blur',
      'expo-clipboard',
      'expo-constants',
      'expo-dev-client',
      'expo-dev-launcher',
      'expo-dev-menu',
      'expo-dev-menu-interface',
      'expo-device',
      'expo-eas-client',
      'expo-file-system',
      'expo-font',
      'expo-haptics',
      'expo-image-loader',
      'expo-image-manipulator',
      'expo-json-utils',
      'expo-keep-awake',
      'expo-localization',
      'expo-mail-composer',
      'expo-manifests',
      'expo-modules-core',
      'expo-notifications',
      'expo-screen-orientation',
      'expo-sharing',
      'expo-splash-screen',
      'expo-store-review',
      'expo-structured-headers',
      'expo-system-ui',
      'expo-updates',
      'expo-updates-interface'
    ],
    staticConfigPath: {},
    pluginHistory: {
      'sentry-expo': {
        name: 'sentry-expo',
        version: '4.2.0'
      },
      'react-native-quick-actions': {
        name: 'react-native-quick-actions',
        version: 'UNVERSIONED'
      },
      'expo-screen-orientation': {
        name: 'expo-screen-orientation',
        version: '4.2.0'
      },
      'react-native-maps': {
        name: 'react-native-maps',
        version: 'UNVERSIONED'
      },
      'expo-ads-admob': {
        name: 'expo-ads-admob',
        version: 'UNVERSIONED'
      },
      'expo-apple-authentication': {
        name: 'expo-apple-authentication',
        version: 'UNVERSIONED'
      },
      'expo-contacts': {
        name: 'expo-contacts',
        version: 'UNVERSIONED'
      },
      'expo-notifications': {
        name: 'expo-notifications',
        version: '0.15.3'
      },
      'expo-updates': {
        name: 'expo-updates',
        version: '0.13.2'
      },
      'expo-branch': {
        name: 'expo-branch',
        version: 'UNVERSIONED'
      },
      'expo-document-picker': {
        name: 'expo-document-picker',
        version: 'UNVERSIONED'
      },
      'expo-facebook': {
        name: 'expo-facebook',
        version: 'UNVERSIONED'
      },
      'expo-system-ui': {
        name: 'expo-system-ui',
        version: '1.2.0'
      },
      'expo-splash-screen': {
        name: 'expo-splash-screen',
        version: '0.15.1'
      },
      'expo-navigation-bar': {
        name: 'expo-navigation-bar',
        version: 'UNVERSIONED'
      },
      'expo-dev-menu': {
        name: 'expo-dev-menu',
        version: '1.0.0'
      },
      'expo-dev-launcher': {
        name: 'expo-dev-launcher',
        version: '1.0.0'
      },
      'expo-dev-client': {
        name: 'expo-dev-client',
        version: '1.0.0'
      },
      'expo-file-system': {
        name: 'expo-file-system',
        version: '14.0.0'
      }
    },
    modResults: {
      android: {
        gradleProperties: [
          {
            type: 'property',
            key: 'expo.jsEngine',
            value: 'hermes'
          }
        ],
        manifest: {
          manifest: {
            '$': {
              'xmlns:android': 'http://schemas.android.com/apk/res/android',
              package: 'im.outa.AmbitoDolar'
            },
            'uses-permission': [
              {
                '$': {
                  'android:name': 'android.permission.INTERNET'
                }
              },
              {
                '$': {
                  'android:name': 'android.permission.SYSTEM_ALERT_WINDOW'
                }
              },
              {
                '$': {
                  'android:name': 'android.permission.READ_EXTERNAL_STORAGE'
                }
              },
              {
                '$': {
                  'android:name': 'android.permission.WRITE_EXTERNAL_STORAGE'
                }
              }
            ],
            queries: [
              {
                intent: [
                  {
                    action: [
                      {
                        '$': {
                          'android:name': 'android.intent.action.VIEW'
                        }
                      }
                    ],
                    category: [
                      {
                        '$': {
                          'android:name': 'android.intent.category.BROWSABLE'
                        }
                      }
                    ],
                    data: [
                      {
                        '$': {
                          'android:scheme': 'https'
                        }
                      }
                    ]
                  }
                ]
              }
            ],
            application: [
              {
                '$': {
                  'android:name': '.MainApplication',
                  'android:label': '@string/app_name',
                  'android:icon': '@mipmap/ic_launcher',
                  'android:roundIcon': '@mipmap/ic_launcher_round',
                  'android:allowBackup': 'false',
                  'android:theme': '@style/AppTheme',
                  'android:usesCleartextTraffic': 'true'
                },
                'meta-data': [
                  {
                    '$': {
                      'android:name': 'expo.modules.updates.EXPO_UPDATE_URL',
                      'android:value': 'https://exp.host/@outatime/ambito-dolar'
                    }
                  },
                  {
                    '$': {
                      'android:name': 'expo.modules.updates.EXPO_SDK_VERSION',
                      'android:value': '45.0.0'
                    }
                  },
                  {
                    '$': {
                      'android:name': 'expo.modules.updates.ENABLED',
                      'android:value': 'true'
                    }
                  },
                  {
                    '$': {
                      'android:name': 'expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH',
                      'android:value': 'ALWAYS'
                    }
                  },
                  {
                    '$': {
                      'android:name': 'expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS',
                      'android:value': '0'
                    }
                  },
                  {
                    '$': {
                      'android:name': 'expo.modules.notifications.default_notification_icon',
                      'android:resource': '@drawable/notification_icon'
                    }
                  },
                  {
                    '$': {
                      'android:name': 'expo.modules.notifications.default_notification_color',
                      'android:resource': '@color/notification_icon_color'
                    }
                  }
                ],
                activity: [
                  {
                    '$': {
                      'android:name': '.MainActivity',
                      'android:label': '@string/app_name',
                      'android:configChanges': 'keyboard|keyboardHidden|orientation|screenSize|uiMode',
                      'android:launchMode': 'singleTask',
                      'android:windowSoftInputMode': 'adjustPan',
                      'android:theme': '@style/Theme.App.SplashScreen'
                    },
                    'intent-filter': [
                      {
                        action: [
                          {
                            '$': {
                              'android:name': 'android.intent.action.MAIN'
                            }
                          }
                        ],
                        category: [
                          {
                            '$': {
                              'android:name': 'android.intent.category.LAUNCHER'
                            }
                          }
                        ]
                      },
                      {
                        action: [
                          {
                            '$': {
                              'android:name': 'android.intent.action.VIEW'
                            }
                          }
                        ],
                        category: [
                          {
                            '$': {
                              'android:name': 'android.intent.category.DEFAULT'
                            }
                          },
                          {
                            '$': {
                              'android:name': 'android.intent.category.BROWSABLE'
                            }
                          }
                        ],
                        data: [
                          {
                            '$': {
                              'android:scheme': 'im.outa.AmbitoDolar'
                            }
                          },
                          {
                            '$': {
                              'android:scheme': 'exp+ambito-dolar'
                            }
                          }
                        ]
                      }
                    ]
                  },
                  {
                    '$': {
                      'android:name': 'com.facebook.react.devsupport.DevSettingsActivity'
                    }
                  }
                ]
              }
            ]
          }
        },
        colors: {
          resources: {
            color: [
              {
                '$': {
                  name: 'iconBackground'
                },
                _: '#FFFFFF'
              },
              {
                '$': {
                  name: 'colorPrimary'
                },
                _: '#023c69'
              },
              {
                '$': {
                  name: 'colorPrimaryDark'
                },
                _: '#FFFFFF'
              },
              {
                '$': {
                  name: 'splashscreen_background'
                },
                _: '#FFFFFF'
              },
              {
                '$': {
                  name: 'notification_icon_color'
                },
                _: '#00AE6B'
              }
            ]
          }
        },
        strings: {
          resources: {
            string: [
              {
                '$': {
                  name: 'app_name'
                },
                _: 'Ámbito Dólar'
              },
              {
                '$': {
                  name: 'expo_splash_screen_resize_mode',
                  translatable: 'false'
                },
                _: 'contain'
              },
              {
                '$': {
                  name: 'expo_splash_screen_status_bar_translucent',
                  translatable: 'false'
                },
                _: 'false'
              },
              {
                '$': {
                  name: 'expo_system_ui_user_interface_style',
                  translatable: 'false'
                },
                _: 'automatic'
              }
            ]
          }
        },
        styles: {
          resources: {
            '$': {
              'xmlns:tools': 'http://schemas.android.com/tools'
            },
            style: [
              {
                '$': {
                  name: 'AppTheme',
                  parent: 'Theme.AppCompat.Light.NoActionBar'
                },
                item: [
                  {
                    '$': {
                      name: 'colorPrimary'
                    },
                    _: '@color/colorPrimary'
                  },
                  {
                    '$': {
                      name: 'colorPrimaryDark'
                    },
                    _: '@color/colorPrimaryDark'
                  }
                ]
              },
              {
                '$': {
                  name: 'Theme.App.SplashScreen',
                  parent: 'AppTheme'
                },
                item: [
                  {
                    '$': {
                      name: 'android:windowBackground'
                    },
                    _: '@drawable/splashscreen'
                  }
                ]
              }
            ]
          }
        },
        colorsNight: {
          resources: {
            color: [
              {
                '$': {
                  name: 'splashscreen_background'
                },
                _: '#000000'
              }
            ]
          }
        }
      },
      ios: {
        infoPlist: {
          CFBundleDevelopmentRegion: 'es',
          CFBundleExecutable: '$(EXECUTABLE_NAME)',
          CFBundleIdentifier: '$(PRODUCT_BUNDLE_IDENTIFIER)',
          CFBundleName: '$(PRODUCT_NAME)',
          CFBundlePackageType: '$(PRODUCT_BUNDLE_PACKAGE_TYPE)',
          CFBundleInfoDictionaryVersion: '6.0',
          CFBundleSignature: '????',
          LSRequiresIPhoneOS: true,
          UILaunchStoryboardName: 'SplashScreen',
          UIRequiredDeviceCapabilities: [
            'armv7'
          ],
          UIViewControllerBasedStatusBarAppearance: false,
          UIStatusBarStyle: 'UIStatusBarStyleDefault',
          'UISupportedInterfaceOrientations~iphone': [
            'UIInterfaceOrientationPortrait'
          ],
          'UISupportedInterfaceOrientations~ipad': [
            'UIInterfaceOrientationLandscapeLeft',
            'UIInterfaceOrientationLandscapeRight'
          ],
          LSApplicationQueriesSchemes: [
            'tweetbot',
            'twitter',
            'tg',
            'instagram',
            'fb',
            'apollo',
            'reddit',
            'com.hammerandchisel.discord',
            'slack',
            'github'
          ],
          CFBundleShortVersionString: '6.0.0',
          CFBundleVersion: '65',
          ITSAppUsesNonExemptEncryption: false,
          CFBundleURLTypes: [
            {
              CFBundleURLSchemes: [
                'im.outa.AmbitoDolar'
              ]
            },
            {
              CFBundleURLSchemes: [
                'exp+ambito-dolar'
              ]
            }
          ],
          UIRequiresFullScreen: true,
          UISupportedInterfaceOrientations: [
            'UIInterfaceOrientationPortrait',
            'UIInterfaceOrientationPortraitUpsideDown',
            'UIInterfaceOrientationLandscapeLeft',
            'UIInterfaceOrientationLandscapeRight'
          ],
          CFBundleDisplayName: 'Ámbito Dólar',
          UIUserInterfaceStyle: 'Automatic',
          UIApplicationShortcutItems: [
            {
              UIApplicationShortcutItemIconType: 'UIApplicationShortcutIconTypeCompose',
              UIApplicationShortcutItemTitle: 'Conversor',
              UIApplicationShortcutItemType: 'Conversion'
            }
          ],
          NSAppTransportSecurity: {
            NSAllowsArbitraryLoads: true,
            NSExceptionDomains: {
              localhost: {
                NSExceptionAllowsInsecureHTTPLoads: true
              }
            }
          }
        },
        entitlements: {
          'aps-environment': 'development'
        },
        expoPlist: {
          EXUpdatesEnabled: true,
          EXUpdatesCheckOnLaunch: 'ALWAYS',
          EXUpdatesLaunchWaitMs: 0,
          EXUpdatesURL: 'https://exp.host/@outatime/ambito-dolar',
          EXUpdatesSDKVersion: '45.0.0'
        },
        podfileProperties: {
          'expo.jsEngine': 'hermes'
        }
      }
    }
  },
  mods: {
    android: {
      manifest: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      colors: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      strings: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      styles: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      colorsNight: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      gradleProperties: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      }
    },
    ios: {
      infoPlist: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      entitlements: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      expoPlist: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      },
      podfileProperties: [AsyncFunction: interceptingMod] {
        isProvider: true,
        isIntrospective: true
      }
    }
  },
  androidStatusBar: {
    backgroundColor: '#FFFFFF'
  }
}

I can confirm that the workaround mentioned above is working for me. App correctly stays locked in PORTRAIT_UP until I unlock or lock to a different orientation. Also no issues after backgrounding/foregrounding the app. I am using SDK 43 managed.

These “same here”/“me too” comments aren’t helpful at all. Please consider only replying to issues with new information.

And for those who feel like it’s taking the maintainers too long to fix this: keep in mind that this is open-source software. Expo is a complex piece of software. If you want it fixed ASAP, create a pull request.

Hitting this issue now. I don’t use EAS build but lockAsync still does nothing on iOS 16.

After digging around the internets for quite a while I found this issue. Looks like using setValue:forKey: shouldn’t be used for setting the orientation from iOS 16 onwards.

My temporary solution to get rotation to work properly was,

App.tsx

React.useLayoutEffect(() => {
    ScreenOrientation.unlockAsync();
}, []);

Confirming that work for me as well.

This solution only works in expo go , doesn’t work in IOS after doing EAS Build!

UPDATE: I found a workaround for the rotation issue on Android at app start by programmatically setting orientation to portrait in my app.config.ts for Android, but leaving it out for iOS like this:

const config: ExpoConfig = {
   ...(process.env.PLATFORM === 'ios' ? {} : { orientation: 'portrait' })
}

There’s more in our config than that, obviously, but that’s the only thing I needed for the orientation. process.env.PLATFORM we set to either ios orandroid during our build depending on the platform being built. It won’t be set when running in Expo Go so the orientation will end up being locked to portrait. Which is fine since this issue only affects EAS built apps.

We’ve seen screen orientation issues in 42, resolved in 43, and then reintroduced in 44. In our case we set app.config.js orientation: ‘landscape’ and lock the position in App.js using AppLoading component startAsync prop. In this case, we see the screen rotate erratically

a quick update, setting the expo.ios.infoPlist option in app.json to support landscape like so:

"infoPlist": {
    "UISupportedInterfaceOrientations": [
        "UIInterfaceOrientationLandscapeRight",
        "UIInterfaceOrientationLandscapeLeft",
        "UIInterfaceOrientationPortrait"
    ],
    "UISupportedInterfaceOrientations~ipad": [
      "UIInterfaceOrientationLandscapeRight",
      "UIInterfaceOrientationLandscapeLeft",
      "UIDeviceOrientationPortrait",
      "UIDeviceOrientationPortraitUpsideDown"
    ]
}

does enable rotating the screen into landscape.

however, expo-screen-orientation still fails to override the orientation locks at runtime to disable landscape orientation when it is not wanted.

so, i think this at least rules out landscape being disabled in the build settings as the reason that expo-screen-orientation could not allow landscape orientation, since it now cannot disallow landscape at runtime either.

@mvanroon thanks, I’ll try that

Just in case, https://github.com/yamill/react-native-orientation worked as expected for last 2 years almost without issues. Today I saw issue in Crashlytics on android 8 and decided to give expo-orientation another try (I tried to migrate 2 months ago and stumped into this issue that lib didn’t work as expected)

    Orientation.lockPlatformAsync({
      screenOrientationArrayIOS: [Orientation.Orientation.PORTRAIT_UP]
    })
    Orientation.lockAsync(Orientation.OrientationLock.PORTRAIT_UP)

This code doesn’t help Orientation still unlocked

UPD. I switched to https://github.com/wonday/react-native-orientation-locker since couldn’t get expo-orientation working

For me, adding expo-sensors as a dependency solved the issues I was having with this library

Don’t forget to make a new development build after adding the new package

I also deleted the “orientation” param from app.json and instead configured the plugin like so:

    "plugins": [
      [
        "expo-screen-orientation",
        {
          "initialOrientation": "DEFAULT"
        }
      ],
    ],

Using this method I managed to solve our issues with expo-screen-orientation. Interestingly enough just adding expo-sensors as a dependency and removing the expo orientation param solved the problems we were having on ios. We did not test if the issue persisted on Expo GO, but it works on our latest EAS build.

The problem occurs when you try to change the screen orientation to landscape when switching to another page while the screen orientation is portrait. If you change the screen orientation on the same page (or screen) there would be no error. So my fix is to call the new page in portrait mode and put a timer. After the timer finish turn the screen. The error will be fixed.

componentDidMount () {
    setTimeout( async () => {
        await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT); 
    }, 1000);
    this.fetchData();
}

You can add ActivityIndicator while the timer is working.

Same here. It seems to be not working properly since v44. https://github.com/expo/expo/issues/19690

There are repro steps with a snack for this issue in this other issue I filed: https://github.com/expo/expo/issues/21044

Also encountering this on SDK 47 with Expo Go and EAS builds on iPad 6, since upgrading it to iPadOS 16.1.1. The issue does not occur on our iPhone running iOS 15.5.

Has this been fixed in SDK 48? If so we will upgrade.

FYI, as of iOS 16, the react-native-orientation library & plugin I created no longer works. Seems like iOS 16 changed the way locking orientation works. It would be great if someone from the expo team took a look at this for expo-screen-orientation and see if it still works for iOS16

My temporary solution to get rotation to work properly was,

App.tsx

    React.useLayoutEffect(() => {
        ScreenOrientation.unlockAsync();
    }, []);

For people looking for a solution, I ejected Expo and went with this one https://github.com/LyraHealth/react-native-orientation-plugin

Another alternative if you are using eas is to create a config-plugin to work with https://github.com/yamill/react-native-orientation. I’ve moved completely off expo-screen-orientation because of this bug.

Surprisingly even though the expo-screen-orientation library fails to detect orientation changes, DeviceMotion does not.

So, as a workaround for now if you only need 1 or a few screens to rotate like i did, you can listen for orientation changes from DeviceMotion in the expo-sensors library and rotate the screen yourself using reanimated (or whatever your animation library of choice is).

@silberistgold if you remove orientation option in app.json, it will work again… but its no good solution