facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.05k stars 24.31k forks source link

Dimensions.get('window').height returns wrong height on Android with notch #23693

Closed onitzschke closed 4 years ago

onitzschke commented 5 years ago

🐛 Bug Report

Dimensions.get('window').height returns wrong height on Android with notch Device: Android One Mi A2 Lite

Environment

React Native Environment Info: System: OS: macOS 10.14.3 CPU: (12) x64 Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz Memory: 1013.34 MB / 16.00 GB Shell: 5.3 - /bin/zsh Binaries: Node: 11.6.0 - /usr/local/bin/node Yarn: 1.7.0 - /usr/local/bin/yarn npm: 6.8.0 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman SDKs: Android SDK: API Levels: 18, 23, 25, 26, 27, 28 Build Tools: 19.1.0, 20.0.0, 21.1.2, 22.0.1, 23.0.1, 23.0.2, 23.0.3, 24.0.0, 24.0.1, 24.0.2, 24.0.3, 25.0.0, 25.0.1, 25.0.2, 25.0.3, 26.0.0, 26.0.1, 26.0.2, 26.0.3, 27.0.0, 27.0.1, 27.0.2, 27.0.3, 28.0.0, 28.0.0, 28.0.0, 28.0.1, 28.0.2, 28.0.3 System Images: android-21 | Intel x86 Atom, android-21 | Google APIs Intel x86 Atom, android-23 | Google APIs Intel x86 Atom, android-23 | Google APIs Intel x86 Atom_64, android-25 | Google APIs ARM EABI v7a, android-27 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom, android-28 | Google Play Intel x86 Atom_64 IDEs: Android Studio: 3.3 AI-182.5107.16.33.5264788 Xcode: /undefined - /usr/bin/xcodebuild npmPackages: react: 16.8.3 => 16.8.3 react-native: ^0.58.5 => 0.58.5 npmGlobalPackages: create-react-native-app: 2.0.2 react-native-cli: 2.0.1 react-native-git-upgrade: 0.2.7

hramos commented 5 years ago

Can you provide more information? What value did the API return vs what was expected, for example.

onitzschke commented 5 years ago

@hramos Xiaomi Mi A2 lite: Dimensions.get('window').height) => 681 Dimensions.get('screen').height) => 760 StatusBar.currentHeight => 31 Google Pixel 3: Dimensions.get('window').height) => 737.4545454545455 Dimensions.get('screen').height) => 785.4545454545455 StatusBar.currentHeight => 24

Expect Dimensions.get('window').height) === 705 on Xiaomi Mi A2 lite to get the same Layout as on Google Pixel 3.

grabbou commented 5 years ago

I believe this is related to issues with Dimensions.get('window') that I outlined here: https://github.com/facebook/react-native/issues/14887#issuecomment-474455052

terryttsai commented 5 years ago

Hi I'm seeing the same issue. Dimensions.get('window').height returns the wrong dimensions for me as well on my Essential Phone.

Dimensions.get('screen').height: 853.3333333333334 Dimensions.get('window').height: 759.6666666666666

So I took a screenshot and measured out the pixels. I measured the soft menu bar to be 48 logical pixels. So I expect Dimensions.get('window').height to return 853.333 - 48 = 805.333 but instead I get 759.666. I don't subtract the status bar height because according to the docs, Dimensions.get('window').height should include the status bar height if the status bar is translucent, which it is.

ouabing commented 5 years ago

Same issue on Mi 8 Lite.

Martijnkerr commented 5 years ago

Issue is on the new Samsung s10 model too.

alexandrius commented 5 years ago

My workaround was to calculate root view height with onLayout.

andrey-shostik commented 5 years ago

same on redmi note 7

danilojpferreira commented 5 years ago

I fix this like this: Import { Dimensions, StatusBar } from 'react-native';

static height = Platform.OS === 'android' ? Dimensions.get('screen').height - StatusBar.currentHeight : Dimensions.get('window').height;

franzwarning commented 5 years ago

True on Samsung Galaxy s10 model.

danilojpferreira commented 5 years ago

I fix this like this: Import { Dimensions, StatusBar } from 'react-native';

static height = Platform.OS === 'android' ? Dimensions.get('screen').height - StatusBar.currentHeight : Dimensions.get('window').height;

I trying now fix (temporarily)like this: Import { Dimensions, StatusBar } from 'react-native';

static height = Platform.OS === 'android' && Platform.Version > 26 ? Dimensions.get('screen').height - StatusBar.currentHeight : Dimensions.get('window').height;

rahamin1 commented 5 years ago

Same problem with react-native 0.59.9, react 16.8.3

Dimensions.get('window').height returns:

React native info:

React Native Environment Info: System: OS: Windows 10 CPU: (8) x64 Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz Memory: 1.80 GB / 7.87 GB Binaries: Yarn: 1.16.0 - C:\Users\yossi\AppData\Roaming\npm\yarn.CMD npm: 6.1.0 - C:\Program Files\nodejs\npm.CMD IDEs: Android Studio: Version 3.3.0.0 AI-182.5107.16.33.5314842

dhruvdangi commented 5 years ago

Based on the phones I've tested, for devices giving wrong height, Dimensions.get('screen').height is not equal to Dimensions.get('window').height. If StatusBar.currentHeight > 24, we consider that it's a device with a notch Here is a temporary fix that I'm using:


      Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24
        ? Dimensions.get('window').height - StatusBar.currentHeight
        : Dimensions.get('window').height
andrey-shostik commented 5 years ago

@dhruvdangi exist another case with bottom navigation bar, its can be hid or showed.

alexandrius commented 5 years ago

Just create something like this in app render function. There's small startup speed impact but solves the height issue

if (this.state.windowHeight === 0) {
    return <View onLayout={({ nativeEvent }) => {
        const windowHeight = nativeEvent.layout.height;
        Defaults.windowHeight = windowHeight;
        this.setState({ windowHeight })
    }} style={{ flex: 1 }} />
}
jakobinn commented 5 years ago

You can use the following package to fix this issue: https://github.com/Sunhat/react-native-extra-dimensions-android

stale[bot] commented 4 years ago

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

rahamin1 commented 4 years ago

Dear bot;

As far as I am aware, the problem described here is not solved. I tried to add a label, but couldn't find a way to do that.

codeplaygoa commented 4 years ago

Based on the phones I've tested, for devices giving wrong height, Dimensions.get('screen').height is not equal to Dimensions.get('window').height. If StatusBar.currentHeight > 24, we consider that it's a device with a notch Here is a temporary fix that I'm using:

      Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24
        ? Dimensions.get('window').height - StatusBar.currentHeight
        : Dimensions.get('window').height

unfortunately this didnt work but i modified your code to work for me Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24 ? Dimensions.get('screen').height - StatusBar.currentHeight : Dimensions.get('window').height minus from screen height instead of window

jsamr commented 4 years ago

Same issue on OnePlus 7. Even though I appreciate the work done by RN Team, I find the failing of such basic, elementary functionality for so long astonishing. Perhaps the blame is on Android API, I don't know. But it's exasperating.

GalB-TenderMarket commented 4 years ago

I solved s10 height issues using this:

const height = Platform.OS === 'android' && Platform.Version > 26 ? ExtraDimensions.getRealWindowHeight() - ExtraDimensions.getSoftMenuBarHeight() - StatusBar.currentHeight
 : Dimensions.get('window').height- StatusBar.currentHeight;
kopax commented 4 years ago

I am having the same issue on an iPhone 6 with chrome and safari.

@GalB-TenderMarket what is ExtraDimensions?

GalB-TenderMarket commented 4 years ago

I am having the same issue on an iPhone 6 with chrome and safari.

@GalB-TenderMarket what is ExtraDimensions?

react-native-extra-dimensions-android npm package

wmadfaa commented 4 years ago

here is how I worked around this bug

import React, { useState, useContext } from "react";
import { Dimensions, View, LayoutChangeEvent } from "react-native";

export default {
  window: {
    width: Dimensions.get("window").width,
    height: Dimensions.get("window").height,
  },
};

const DEFAULT_DIMENSIONS_CONTEXT_VALUE = {
  width: Dimensions.get("window").width,
  height: Dimensions.get("window").height,
};

const DIMENSIONS_CONTEXT = React.createContext(DEFAULT_DIMENSIONS_CONTEXT_VALUE);

export const DimensionsProvider: React.FC = ({ children }) => {
  const [layout, setLayout] = useState(DEFAULT_DIMENSIONS_CONTEXT_VALUE);

  const handleLayout = ({ nativeEvent }: LayoutChangeEvent) => {
    const { width, height } = nativeEvent.layout;
    setLayout({ width, height });
  };

  return (
    <View onLayout={handleLayout} style={{ flex: 1 }}>
      <DIMENSIONS_CONTEXT.Provider value={layout}>{children}</DIMENSIONS_CONTEXT.Provider>
    </View>
  );
};

export const useDimensions = () => {
  const dimensions = useContext(DIMENSIONS_CONTEXT);
  return dimensions;
};
jsamr commented 4 years ago

@wmadfaa That is a workaround for the bug but the bug is still here.

Bardiamist commented 4 years ago

Faced with it. Very unpleasant bug.

stevenyap commented 4 years ago

I am having this bug too.

dragos99 commented 4 years ago

Me too..so annoying.

yangdong-wuye commented 4 years ago

+1

trongitnlu commented 4 years ago

It's work for me, you can try

export const deviceHeight = Dimensions.get('window').height;
export const DEVICE_HEIGHT = Platform.select({
  ios: deviceHeight,
  android:
    StatusBar.currentHeight > 24
      ? deviceHeight
      : deviceHeight - StatusBar.currentHeight,
});
cklinx commented 4 years ago

+1

ease-space commented 4 years ago

+1 same issue tested on xiaomi mi8(real device) and Pixel 3 XL(simulator) according to my observations related CutoutMode(if CutoutMode statusBar height ignore)

hengkx commented 4 years ago

+1

Bardiamist commented 4 years ago
![PHOTO-2020-05-29-11-46-34](https://user-images.githubusercontent.com/22082342/90356258-1703a180-e079-11ea-9aa4-acd6efced5a8.jpg) `Dimensions.get('window').height - StatusBar.currentHeight` works for devices without software bottom navigation bar.
![Screenshot_2020-08-14-18-31-46-631_com valr app](https://user-images.githubusercontent.com/22082342/90356288-38fd2400-e079-11ea-9aad-6126bf595aed.jpg) But then on devices with software bottom navigation bar (Redmi Note 7) that is wrong.

How can I detect that user has software bottom navigation bar?

MichaelKim commented 4 years ago

The workaround by @trongitnlu works for me, but an alternative is to use react-native-safe-area-context and grab the frame width and height from there.

import { useSafeAreaFrame } from 'react-native-safe-area-context';

const Component = props => {
  const frame = useSafeAreaFrame();
  // frame.height
};

A frame equivalent of withSafeAreaInsets doesn't seem to exist, but with a class component you could make your own higher-order component or a wrapper like this:

class Component extends React.Component { ... }

const Frame = props => {
  const frame = useSafeAreaFrame();
  return <Component {...props} height={frame.height} />;
};

or grab directly from SafeAreaFrameContext

class Component extends React.Component {
  render() {
    return (
      <SafeAreaFrameContext.Consumer>
        {(frame) => ...}
      </SafeAreaFrameContext.Consumer>
    );
  }
}

Make sure to wrap your root component with SafeAreaProvider for this to work.

pjivers-bom commented 4 years ago

@LenKagamine That's a really elegant solution. Thank you.

safaiyeh commented 4 years ago

The workaround by @trongitnlu works for me, but an alternative is to use react-native-safe-area-context and grab the frame width and height from there.

import { useSafeAreaFrame } from 'react-native-safe-area-context';

const Component = props => {
  const frame = useSafeAreaFrame();
  // frame.height
};

A frame equivalent of withSafeAreaInsets doesn't seem to exist, but with a class component you could make your own higher-order component or a wrapper like this:

class Component extends React.Component { ... }

const Frame = props => {
  const frame = useSafeAreaFrame();
  return <Component {...props} height={frame.height} />;
};

or grab directly from SafeAreaFrameContext

class Component extends React.Component {
  render() {
    return (
      <SafeAreaFrameContext.Consumer>
        {(frame) => ...}
      </SafeAreaFrameContext.Consumer>
    );
  }
}

Make sure to wrap your root component with SafeAreaProvider for this to work.

Appreciate this work around! Closing, feel free to open another issue if this continues to be an issue thanks everyone.

Bardiamist commented 4 years ago

@safaiyeh Workaround isn't fix.

Also @LenKagamine workaround not helpful in my case. I want to know dememsions before any view rendered for use demension in StyleSheet.

Instead that workaround possible to use just View with onLayout. But I want to fix instead workarounds.

jsamr commented 4 years ago

@safaiyeh With all respects, another workaround is to not use React Native, or change our careers in development. My point is, a workaround is no fix. The bug still exists. Please reopen :-) ﻡﺮﮑﺸﺘﻣ

cristianoccazinsp commented 3 years ago

Looking forward for fixes related to this. I have been testing on a Pixel 5 and got surprised about how terrible the Dimensions and StatusBar components are working.

For starters, the Dimensions API has a different behaviour if the device is in portrait or landscape modes. In portrait, it doesn't seem to account for the status bar height, however, it does include the status bar height in landscape. So the code to get the current screen height has to consider whether or not it is in in portrait or landscape.

Following, the StatusBar API does not update the currentHeight value on orientation changes, so it stays to w/e orientation the app was opened with. Therefore, we cannot use StatusBar.currentHeight on devices that have different status bars depending on the orientation. Ultimately, we cannot use neither Dimensions nor StatusBar to get the exact window height.

As a very ugly work around written above, I am using this, but note that the 28 value is just a magic value from my test device that will most likely not work in every device.

// screen height = windowHeight - getMinusHeight(orientation.isPortrait)

const getMinusHeight = (isPortrait) => {

    // need this to consider notch-based devices
    // update this once RN fixes their height calcs
    if(StatusBar.currentHeight > 24){
      return isPortrait ? 6 : 28;
    }
    return StatusBar.currentHeight;
  }
jsamr commented 3 years ago

I invite you to express your feelings regarding Dimensions module here: https://github.com/react-native-community/discussions-and-proposals/issues/291

zarvedan commented 3 years ago

Did you try to use Dimensions.get("screen").height instead of Dimensions.get("window").height ?

It fixes my issue on Google Pixel 4a.

DanielBoening commented 3 years ago

@safaiyeh this workaround is clearly not applicable to all projects without big rework. With more and more android devices getting screen notches it is not understandable why this does not have high priority and is left to stay. Furthermore I got the assumption that the wrong screen sizes also affect measureView() calculations for those devices.

fabOnReact commented 3 years ago

https://github.com/facebook/react-native/blob/eacc94005b1b1d33d47b3bea88a6fd242560dafb/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.java#L86-L98

https://github.com/facebook/react-native/blob/63e89427a95489e10094a70ce91d6ef87cf3642a/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java#L48-L49

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/content/res/Resources.java

    /**
     * Return the current display metrics that are in effect for this resource
     * object. The returned object should be treated as read-only.
     *
     * <p>Note that the reported value may be different than the window this application is
     * interested in.</p>
     *
     * <p>Best practices are to obtain metrics from {@link WindowManager#getCurrentWindowMetrics()}
     * for window bounds, {@link Display#getRealMetrics(DisplayMetrics)} for display bounds and
     * obtain density from {@link Configuration#densityDpi}. The value obtained from this API may be
     * wrong if the {@link Resources} is from the context which is different than the window is
     * attached such as {@link Application#getResources()}.
     * <p/>
     *
     * @return The resource's current display metrics.
     */
    public DisplayMetrics getDisplayMetrics() {
        return mResourcesImpl.getDisplayMetrics();
    }
dancherb commented 3 years ago

It seems like Dimensions.get('window').height doesn't include StatusBar.currentHeight when StatusBar.currentHeight is >24, giving inconsistent results. Dimensions.get('screen').height (screen instead of width) is also unpredictable. For me, this always had an excess of 48, maybe equivalent to the Android bottom navigation buttons.

You can see how these dimensions given are incorrect with the following example at the root of the app:

<View style={{
  flex: 1,
  backgroundColor: 'red'
}}>
    <View style={{
      width: Dimensions.get('window').width - 2,
      height: Dimensions.get('window').height - 2,
      backgroundColor: 'blue',
      borderWidth: 1,
      borderColor: 'yellow'
    }}/>
</View>

If all the dimensions given were correct, the blue section would perfectly cover the screen, with a 1px yellow border. This is rarely the case - and there is lots of red space visible, or no yellow, inconsistent between devices.

cristianoccazinsp commented 3 years ago

The dimensions module probably has to be externalized since its been almost a year and this is still broken.

dancherb commented 3 years ago

I saw useWindowDimensions() from 'react-native' recommended as the way forward and just migrated to this, but this has the same issue...

valeriik commented 3 years ago

Based on the phones I've tested, for devices giving wrong height, Dimensions.get('screen').height is not equal to Dimensions.get('window').height. If StatusBar.currentHeight > 24, we consider that it's a device with a notch Here is a temporary fix that I'm using:

      Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24
        ? Dimensions.get('window').height - StatusBar.currentHeight
        : Dimensions.get('window').height

unfortunately this didnt work but i modified your code to work for me Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24 ? Dimensions.get('screen').height - StatusBar.currentHeight : Dimensions.get('window').height minus from screen height instead of window

This didn't work for me for few Android devices.

e.g. Xiaomi Redmi 4A: Dimensions.get('screen').height - 640 Dimensions.get('window').height - 640 Status bar height - 25 but REAL Dimensions.get('window').height should be 640-25 = 615 instead of 640!

I have not tested the code below on absolutely all Android devices, so I cannot give a 100% guarantee that this code works on 100% of devices in the world, but it works on all devices that I tested:

import { getStatusBarHeight } from 'react-native-status-bar-height'
import { initialWindowMetrics } from 'react-native-safe-area-context'

export const STATUS_BAR_HEIGHT = getStatusBarHeight()

export const WINDOW_HEIGHT_NO_STATUS_BAR = Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && STATUS_BAR_HEIGHT > 24 
? Dimensions.get('screen').height - STATUS_BAR_HEIGHT 
: STATUS_BAR_HEIGHT > 24 
  ? Dimensions.get('window').height - STATUS_BAR_HEIGHT 
  : Dimensions.get('window').height + initialWindowMetrics.insets.bottom === Dimensions.get('screen').height 
    ? Dimensions.get('window').height - STATUS_BAR_HEIGHT 
    : Dimensions.get('window').height
Aryk commented 2 years ago

Based on the phones I've tested, for devices giving wrong height, Dimensions.get('screen').height is not equal to Dimensions.get('window').height. If StatusBar.currentHeight > 24, we consider that it's a device with a notch Here is a temporary fix that I'm using:

      Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24
        ? Dimensions.get('window').height - StatusBar.currentHeight
        : Dimensions.get('window').height

unfortunately this didnt work but i modified your code to work for me Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && StatusBar.currentHeight > 24 ? Dimensions.get('screen').height - StatusBar.currentHeight : Dimensions.get('window').height minus from screen height instead of window

This didn't work for me for few Android devices.

e.g. Xiaomi Redmi 4A: Dimensions.get('screen').height - 640 Dimensions.get('window').height - 640 Status bar height - 25 but REAL Dimensions.get('window').height should be 640-25 = 615 instead of 640!

I have not tested the code below on absolutely all Android devices, so I cannot give a 100% guarantee that this code works on 100% of devices in the world, but it works on all devices that I tested:

import { getStatusBarHeight } from 'react-native-status-bar-height'
import { initialWindowMetrics } from 'react-native-safe-area-context'

export const STATUS_BAR_HEIGHT = getStatusBarHeight()

export const WINDOW_HEIGHT_NO_STATUS_BAR = Platform.OS !== 'ios' && Dimensions.get('screen').height !== Dimensions.get('window').height && STATUS_BAR_HEIGHT > 24 
? Dimensions.get('screen').height - STATUS_BAR_HEIGHT 
: STATUS_BAR_HEIGHT > 24 
  ? Dimensions.get('window').height - STATUS_BAR_HEIGHT 
  : Dimensions.get('window').height + initialWindowMetrics.insets.bottom === Dimensions.get('screen').height 
    ? Dimensions.get('window').height - STATUS_BAR_HEIGHT 
    : Dimensions.get('window').height

I tried this, but unfortunately doesn't quite work always.

Only thing that seems tried and true is:

import { useSafeAreaFrame } from 'react-native-safe-area-context';
valeriik commented 2 years ago

I tried this, but unfortunately doesn't quite work always. Could you tell please what are these real devices models?