Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect window dimension change when virtual buttons are show/hidden on samsung s8 #14887

Closed
aidan-doherty opened this issue Jul 7, 2017 · 33 comments
Labels
API: Dimensions Bug Platform: Android Android applications. Priority: Mid Stale There has been a lack of activity on this issue and it may be closed soon.

Comments

@aidan-doherty
Copy link

aidan-doherty commented Jul 7, 2017

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
2. 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.

@machard
Copy link
Contributor

machard commented Aug 15, 2017

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

@aidan-doherty
Copy link
Author

@machard Nothing so far :/

@freakyfriday
Copy link

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.

@jorgenavarrochavez
Copy link

this problem happen too with note 8

@stale
Copy link

stale bot commented Jan 13, 2018

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.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Jan 13, 2018
@nhducit
Copy link

nhducit commented Jan 15, 2018

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

@stale stale bot removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Jan 15, 2018
@kyledecot
Copy link
Contributor

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

@react-native-bot react-native-bot added Android Help Wanted :octocat: Issues ideal for external contributors. labels Mar 9, 2018
@kylanhurt
Copy link

Yeah I am having this issue. Help needed!

@Draccan
Copy link

Draccan commented Mar 13, 2018

It seems that we have different ways:

  1. https://stackoverflow.com/questions/44676208/determine-whether-hardware-buttons-are-drawn-on-screen-in-react-native
  2. https://stackoverflow.com/questions/28983621/detect-soft-navigation-bar-availability-in-android-device-progmatically
  3. https://stackoverflow.com/questions/28406506/check-if-android-device-has-software-or-hardware-navigation-buttons

But what about take this functionality directly into React Native?

@el173
Copy link

el173 commented Jul 30, 2018

I am also having this issue in S8

@renandelmonico
Copy link

I'm having this issue in A8

@kelset kelset added Platform: Android Android applications. Resolution: PR Submitted A pull request with a fix has been provided. labels Sep 19, 2018
@kelset
Copy link
Contributor

kelset commented Sep 19, 2018

I think this #20999 will affect this issue.

@io-pan
Copy link

io-pan commented Sep 28, 2018

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,
  },
});

@vcapretz
Copy link

vcapretz commented Oct 4, 2018

@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>
}

@io-pan
Copy link

io-pan commented Oct 7, 2018

@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});
}

@gabgagnon
Copy link

gabgagnon commented Oct 9, 2018

@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...

@keith-kurak
Copy link

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: expo/expo#2399

@c0d3m3nt0r
Copy link

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?

@c0d3m3nt0r
Copy link

@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.

@grabbou
Copy link
Contributor

grabbou commented Mar 19, 2019

@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 #14887 (comment)

I am looking into this right now.

@grabbou
Copy link
Contributor

grabbou commented Mar 19, 2019

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

@grabbou grabbou added API: Dimensions Priority: Mid and removed Help Wanted :octocat: Issues ideal for external contributors. Resolution: PR Submitted A pull request with a fix has been provided. labels Mar 19, 2019
@grabbou
Copy link
Contributor

grabbou commented Mar 22, 2019

Looks like this is going to be fixed in #20999 and I don't think any major rewrites will happen to Dimensions API since this one is meant to replace it.

@wmonecke

This comment has been minimized.

@Zapopozapopo

This comment has been minimized.

@fabOnReact
Copy link
Contributor

fabOnReact commented May 25, 2020

Pull Request #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 #20999

Seems to me a complex change.

@damithg-dev
Copy link

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

@nivas412
Copy link

@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

@kelset
Copy link
Contributor

kelset commented Aug 17, 2020

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.

@kelset kelset removed the Resolution: PR Submitted A pull request with a fix has been provided. label Aug 17, 2020
@stale
Copy link

stale bot commented Dec 25, 2020

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.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Dec 25, 2020
@fabOnReact
Copy link
Contributor

came back reading #14887 (comment) while working on #23693

First one, when you hide the navigation bar, by calling setSystemUiVisibility, 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.

This are my considerations:

SOLUTION 1
implement OnSystemUiVisibilityChangeListener for API < 30 as previously done with ViewTreeObserver.OnGlobalLayoutListener

private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/view/View.java

  /**
   * Interface definition for a callback to be invoked when the status bar changes visibility. This
   * reports <strong>global</strong> changes to the system UI state, not what the application is
   * requesting.
   *
   * @see
   *     View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
   * @deprecated Use {@link WindowInsets#isVisible(int)} to find out about system bar visibilities
   *     by setting a {@link OnApplyWindowInsetsListener} on this view.
   */
  @Deprecated
  public interface OnSystemUiVisibilityChangeListener {
    /**
     * Called when the status bar changes visibility because of a call to {@link
     * View#setSystemUiVisibility(int)}.
     *
     * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE}, {@link
     *     #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, and {@link #SYSTEM_UI_FLAG_FULLSCREEN}. This tells you
     *     the <strong>global</strong> state of these UI visibility flags, not what your app is
     *     currently applying.
     */
    public void onSystemUiVisibilityChange(int visibility);
  }

SOLUTION 2

This is the solution implemented by Janic Duplessis in pr #20999 to somehow (I still don't understand) trigger the update with the objc libraries
https://github.com/facebook/react-native/pull/20999/files#diff-8105b26fef7a80650540da3c9f04b22ddebd08db8aec8f6c8360de756f3d93afR403

@stale stale bot removed the Stale There has been a lack of activity on this issue and it may be closed soon. label Mar 30, 2021
@fabOnReact
Copy link
Contributor

fabOnReact commented Mar 30, 2021

about point 2 of #14887 (comment)

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.

as this is relevant for my pr #30919 I notice the deprecation and started testing alternative solution with getScreenDisplayMetrics, which seems to take in consideration the Notch and the Navigation

/**
* @deprecated Use {@link #getScreenDisplayMetrics()} instead. See comment above as to why this is
* not correct to use.
*/
@Deprecated
public static DisplayMetrics getWindowDisplayMetrics() {
return sWindowDisplayMetrics;
}

I guess it may not be compatible with the calculation of the Keyboard Height based on getWindowVisibleDisplayFrame

getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
final int heightDiff =
DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom;

the calculation of the keyboard height is the difference between:

  • the screen height without navigation obtained with deprecated getWindowDisplayMetrics().heightPixels (1776/1632 with/out notch)
  • mVisibleViewArea.bottom obtained with getWindowVisibleDisplayFrame

it is not possible to use getScreenDisplayMetrics because returns the available height of the screen which includes the area of the notch, but also the navigation bar (1920 in my device)

For example in the specific case of calculating the soft-keyboard height, mVisibleViewArea.bottom does not include the navigation bar, while getScreenDisplayMetrics().heightPixels includes the navigation.

The result of using getScreenDisplayMetrics().heightPixels without substracting the navigation height.

CLICK TO OPEN ANDROID SOURCECODE

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/view/View.java
https://android.googlesource.com/platform/frameworks/base/+/d54f3f41c4b41955b7b4382a08b97a356b31fde4/core/java/android/view/View.java#5316

  /**
   * Retrieve the overall visible display size in which the window this view is attached to has been
   * positioned in. This takes into account screen decorations above the window, for both cases
   * where the window itself is being position inside of them or the window is being placed under
   * then and covered insets are used for the window to position its content inside. In effect, this
   * tells you the available area where content can be placed and remain visible to users.
   *
   * <p>This function requires an IPC back to the window manager to retrieve the requested
   * information, so should not be used in performance critical code like drawing.
   *
   * @param outRect Filled in with the visible display frame. If the view is not attached to a
   *     window, this is simply the raw display size.
   */
  public void getWindowVisibleDisplayFrame(Rect outRect) {
    if (mAttachInfo != null) {
      try {
        mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
      } catch (RemoteException e) {
        return;
      }
      // XXX This is really broken, and probably all needs to be done
      // in the window manager, and we need to know more about whether
      // we want the area behind or in front of the IME.
      final Rect insets = mAttachInfo.mVisibleInsets;
      outRect.left += insets.left;
      outRect.top += insets.top;
      outRect.right -= insets.right;
      outRect.bottom -= insets.bottom;
      return;
    }
    // The view is not attached to a display so we don't have a context.
    // Make a best guess about the display size.
    Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
    d.getRectSize(outRect);
  }

I agree that this, #23693 and #30919 problems should all be solved at their root (in DisplayMetricsHolder??), but I also find very complex understanding the potential effects on other functionalities in ReactAndroid.

I reviewed the solution proposed in https://github.com/facebook/react-native/pull/20999/files#diff-c6c68fb9580db5698d241fdd97b31a25ca1fdc061023a1bf1959f64fc4a00743R492-R512 by Janic Duplessis and I hopefully I will find a solution in the upcoming days. Thanks ☮️

@github-actions
Copy link

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@github-actions github-actions bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Jul 20, 2023
@github-actions
Copy link

This issue was closed because it has been stalled for 7 days with no activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API: Dimensions Bug Platform: Android Android applications. Priority: Mid Stale There has been a lack of activity on this issue and it may be closed soon.
Projects
None yet
Development

No branches or pull requests