React-native: Detect window dimension change when virtual buttons are show/hidden on samsung s8

Created on 7 Jul 2017  路  28Comments  路  Source: facebook/react-native

Is this a bug report?

Yes

Have you read the Bugs section of the Contributing to React Native Guide?

Yes

Environment

  1. react-native -v:0.44.2
  2. node -v:7.4.0
  3. npm -v:4.0.0
  4. yarn --version (if you use Yarn):

Then, specify:

  1. Target Platform (e.g. iOS, Android):Android
  2. Development Operating System (e.g. macOS Sierra, Windows 10):Linux mint
  3. Build tools (Xcode or Android Studio version, iOS or Android SDK version, if relevant):

Steps to Reproduce

(Write your steps here:)

1.use Dimensions.get("window").height

  1. On the samsung s8 the virtual home/back button can be hidden so i will toggle this on or of and a layout change is detected but the value is not changed but is when i change device orientation

Expected Behavior

(Write what you thought would happen.)

I expect the window height to change from 692 to 716 on my device anyway whenever the buttons are hidden.

Actual Behavior

The height should be changing to match the window height change when this is hidden. If i use the nativeEvent.layout the correct height value is shown but using this means the height is always undefined on first render which is not ideal.

Reproducible Demo

I don't think a snack example will be any good as i cant reproduce this issue on it but i will add a gif showing the issue on my device.

ezgif com-video-to-gif

As you can see above when the orientation changes the height changes to 360 but when i toggle the virtual home buttons the height is not changing samsung s8 screen resolution is 360x740 pixels if that is helpful.

Sorry a gif was the only way i could think to show this as a actual demo, Wouldn't show anything without the code on the correct device.

Dimensions Bug Mid-Pri Android

Most helpful comment

This issue haven't been fixed. Please keep it open Stale bot

All 28 comments

@aidan2129 any news on that ?
When opening the app from a notification, the space for the bottom bar remain blank

@machard Nothing so far :/

I've got the same issue. I tried https://github.com/Sunhat/react-native-extra-dimensions-android which gives the size of the virtual menu bar but it doesn't tell you if the virtual menu bar is showing or not.

this problem happen too with note 8

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.

This issue haven't been fixed. Please keep it open Stale bot

I'm currently experiencing this issue as well. I attempted to use https://github.com/mockingbot/react-native-immersive but this doesn't seem to work on the S8

Yeah I am having this issue. Help needed!

I am also having this issue in S8

I'm having this issue in A8

I think this https://github.com/facebook/react-native/pull/20999 will affect this issue.

Same on S9 .
it always returns screen size minus virtual buttons height
while my app opens without virtual buttons shown.

So I went into this ugly hack in my main component:

...
  getWindowDimension(event) {
    this.device_width = event.nativeEvent.layout.width,
    this.device_height = event.nativeEvent.layout.height

    console.log (this.device_height);  // Yeah !! good value
    console.log (this.device_width); 
  }
...

  render() {
    return (
      <View 
        style={styles.container}
        onLayout={(event) => this.getWindowDimension(event)} 
        >
            ....
      </View>

...
const styles = StyleSheet.create({
  container: {
    position:'absolute',
    left:0,
    right:0,
    top:0,
    bottom:0,
  },
});

@io-pan where did you put this code? I tried setting this to the view both inside and outside the Modal, but it didn't work, here's a code snippet:

render() {
  <View
        style={{
          position: 'absolute',
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
        }}
        onLayout={event => this.getWindowDimension(event)}
      >
        <Modal
          visible
          transparent
          animationType="fade"
          presentationStyle="overFullScreen"
          onRequestClose={onRequestClose}
        >
          <NativeOverlayModalContent
            modalContainerStyles={modalContainerStyles}
            modalBodyStyles={modalBodyStyles}
            disableScroll={disableScroll}
          >
            <View> {children} </View>
          </NativeOverlayModalContent>
        </Modal>
      </View>
}

@vcapretz I put it on the root view of my root component. getWindowDimension is triggered when I lock/unlock virtual buttons and when I change the orientation of my phone (portrait / lanscape).
You may store the event.nativeEvent.layout.height&width into your state if you want it to be dynamic. i.e

  getWindowDimension(event) {
    this.setState({device_height: event.nativeEvent.layout.height});
}

@io-pan This is a good idea. It would be wonderful if React-Native had an eventListener when the hardware-bar is toggle/untoggle. addEventListener() would be great but Dimensions props never change...

I'm an Expo user who has been grappling with this issue the past few weeks, and I was just able to reproduce the issue on my Galaxy S8 and then make it go away by changing the splash.resizeMode setting in app.json from cover to contain. I think behind the scenes this may effect the activity theme defined in AndroidManifest.xml, but I'm not sure how and I haven't poked around with this in a vanilla RN project yet, but figured I'd post this in case this is useful to someone who might know what those values effect.

Here's the issue: https://github.com/expo/expo/issues/2399

It is also happening to me on Galaxy S8+ and RN 0.55.4. The issue goes away if you force to re-render the inner View component in:

// /Libraries/Modal/Modal.js >> Modal.render()
...
return (
      <RCTModalHostView
        animationType={animationType}
        presentationStyle={presentationStyle}
        transparent={this.props.transparent}
        hardwareAccelerated={this.props.hardwareAccelerated}
        onRequestClose={this.props.onRequestClose}
        onShow={this.props.onShow}
        identifier={this._identifier}
        style={styles.modal}
        onStartShouldSetResponder={this._shouldSetResponder}
        supportedOrientations={this.props.supportedOrientations}
        onOrientationChange={this.props.onOrientationChange}>
-->>    <View style={[styles.container, containerStyles]}>{innerChildren}</View>
      </RCTModalHostView>
    );

I came up with that reasoning after adding a state variable as part of the inner View style property, and then forcing its value to change when Modal.onShow is called, that way the view is forced to be re-rendered, and it does it as expected (considering the full height, without the hardware buttons, which I assume are not present at that point, the same happen when forcing to show/hide hardware buttons), it smells to an event handler being called when the screen dimension changes, but the modal acts as if the event handler was not called the first time the modal is displayed on screen. I was wondering if it could be related to android.widget.FrameLayout, as in com.facebook.react.views.modal.ReactModalHostView.getContentView(), per method docs:

/**
   * Returns the view that will be the root view of the dialog. We are wrapping this in a
   * FrameLayout because this is the system's way of notifying us that the dialog size has changed.
   * This has the pleasant side-effect of us not having to preface all Modals with
   * "top: statusBarHeight", since that margin will be included in the FrameLayout.
   */

It seems that method is related to the height calculation, I'm just wondering, I did not dig deeper yet. In the meantime, I was not able to make the inner View to get refreshed, not even by traversing Modal child components, I tried to find the way to make the modal inner view to re-render from the outside, but I was not able to do it, also, the component does not expose any style property later to the inner view, so I was only able to workaround the issue by changing Module component code, but that is not the solution I'd like to come up with, I do not want to fork another dependency, I'm full of forks and workarounds at this point.

Any thoughts?

@kelset, one question, do you think #20999 could potentially fix this issue?, I see that PR is kind of stale at this point, and in the context of my comment above, I'm not sure that PR will actually fix the issue. Were you able to have a look at this issue from your end?, we are planning to invest some time on it near soon, but I wanted to check if anyone else was working on this first (not a workaround, not changing node_modules files, but an actual change in the codebase to address this issue). We have been looking at it on different Android/Samsung device combinations, and it is reproducible in all of them.

@c0d3m3nt0r I think the PR will fix the issue, because it's going to provide a nice way to measure the available space. It will be really similar to https://github.com/facebook/react-native/issues/14887#issuecomment-427662012

I am looking into this right now.

The problem can be broken down into two parts.

First one, when you hide the navigation bar, by calling setUISystemVisibility, the global onLayout handler is not called. You need to rotate the screen to actually get the Dimensions object to update. We can easily fix that by adding onSetUISystemVisibilityChangeListener.

Second problem is that inside DisplayMetricsHolder we use DisplayMetrics.getMetrics() or getContext().getResources().getDisplayMetrics() and they always seem to return same, incorrect values, regardless whether the software keys are visible or not. Even after rotating the screen, I am still receiving false width/height. Looks like the values are somehow cached. Maybe they are not refreshed.

From the Google documentation:

The application display area specifies the part of the display that may contain an application window, excluding the system decorations. The application display area may be smaller than the real display area because the system subtracts the space needed for decor elements such as the status bar. Use the following methods to query the application display area: getSize(Point), getRectSize(Rect) and getMetrics(DisplayMetrics).

I am not sure if "the system subtracts the space" means it subtracted even when decor elements are not visible.

Either way, we can use getWindowVisibleDisplayFrame that seems to be reporting values properly.

We already use the getWindowVisibleDisplayFrame here to calculate certain sizes related to the keyboard showing on screen.

It seems to be working properly when entering immersive mode on Android too.

I would suggest that we just rewrite the logic inside DisplayMetricsHolder to use getWindowVisibleDisplayFrame instead and get rid of a special code path for keyboard events. That way, KeyboardAvoidingView could use the Device.get('window') directly and it would also solve the reason why it's been marked as deprecated on Android - it would now return correct values.

I am not even sure why we need DisplayMetricsHolder in first place, instead of directly dispatching the update from ReactRootView, which seems to be the only consumer.

CC: @janicduplessis @hramos please let me know what you think

Looks like this is going to be fixed in https://github.com/facebook/react-native/pull/20999 and I don't think any major rewrites will happen to Dimensions API since this one is meant to replace it.

Still happening on RN 0.59.8

Still happening on RN 0.61.4

Pull Request https://github.com/facebook/react-native/pull/20999

There is currently no way to access layout metrics from the root view that contains the component tree. This can be useful when you need to access these values from JS synchronously without having to rely on the onLayout event of a top level view which is async and can cause flickers.

It is roughly the equivalent of the Dimensions module for screen / window size but instead based on the RootView that the component is rendered in with a more modern Context based api. This also be useful for hybrid apps where the root view might not cover the entire screen.

Another goal of this is to be able to expose safeAreaInset values which cannot be accessed currently from JS. SafeAreaView is sometimes not flexible enough if we want to apply the insets manually in JS (for example use margins instead of padding).

The pull request was not merged as it had some performance issues, more infos at https://github.com/facebook/react-native/pull/20999

Seems to me a complex change.

is there any fix or workaround for this. extra dimension library does not update.

@vcapretz I put it on the root view of my root component. getWindowDimension is triggered when I lock/unlock virtual buttons and when I change the orientation of my phone (portrait / lanscape).
You may store the event.nativeEvent.layout.height&width into your state if you want it to be dynamic. i.e

  getWindowDimension(event) {
    this.setState({device_height: event.nativeEvent.layout.height});
}

it is giving 0 value

As mentioned already by a few folks, it seems that the PR that supposedly was going to fix this (#20999) has been actually closed and the solution implemented in a separated library: https://github.com/th3rdwave/react-native-safe-area-context

It doesn't seem that that library provides a dimensions endpoint, though.

Was this page helpful?
0 / 5 - 0 ratings