react-native-safe-area-context icon indicating copy to clipboard operation
react-native-safe-area-context copied to clipboard

useSafeAreaInsets on Android returns 0 for top

Open schmru opened this issue 3 years ago • 17 comments

Hi, I'm using

"react-native-safe-area-context": "3.0.7"
"react-native": "0.62.2"
"expo": "38.0.8"

and when I call const insets = useSafeAreaInsets(); on Android it returns 0 for top inset, on iOS it returns correct number. I packed everything into

<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<SafeAreaView style={styles.safeAreaStyle}>

on top level of navigation.

Am I doing something wrong or is this a bug?

schmru avatar Jul 20 '20 08:07 schmru

Hey, could be wrong with my fix, but I noticed that the insets object for this library doesn't seem to take into account the Android status bar height in the top property of the insets object.

I got around this by adding StatusBar.currentHeight (don't forget to import {StatusBar} from 'react-native';) to the top inset if the app is running on Android.

Hope that helps.

choibles avatar Jul 22 '20 22:07 choibles

@choibles In some Android devices the top will be 0 but in other, it will be a real number like 24, which is the status bar height. What should I do?

Rotemy avatar Jul 31 '20 13:07 Rotemy

Do you have an example of an android device or simulator where the issue happens?

janicduplessis avatar Aug 19 '20 19:08 janicduplessis

Do you have an example of an android device or simulator where the issue happens?

It happens on my Galaxy S10

tomasswood avatar Aug 19 '20 22:08 tomasswood

Same here...Samsung S10e

jamesholcomb avatar Sep 27 '20 18:09 jamesholcomb

Same on S10+

webraptor avatar Sep 29 '20 09:09 webraptor

After some more testing, the issue seems to be isolated to Android 10 + full screen gesture navigation (also repro'd on Pixel 3XL emulator). No problems when using 2 or 3 button nav.

Here is a portion of the MainActivity onCreate to allow drawing behind the nav bar:

    ...
    if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 21) {
      setWindowFlag(this, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, true);
    }

    if (Build.VERSION.SDK_INT >= 19) {
      getWindow().getDecorView().setSystemUiVisibility(
        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
    }

    if (Build.VERSION.SDK_INT >= 21) {
      getWindow().setStatusBarColor(Color.TRANSPARENT);
      getWindow().setNavigationBarColor(Color.TRANSPARENT);
    }

jamesholcomb avatar Sep 29 '20 16:09 jamesholcomb

I think this has to do with having a translucent status bar. I am able to reproduce this on a Pixel 3 Emulator. Sometimes for initialWindowMetrics.frame.y I get 0 and other times I get 24. It also seems to affect initialWindowMetrics.frame.height as sometimes I get 737 and others I get 713 (24 less).

I think there's a race condition going on from when I set the status bar to be translucent.

If I comment out the line where I set the status bar to be translucent, I get 24 every time.

systemride avatar Nov 13 '20 03:11 systemride

Same on S10+

martinezguillaume avatar Nov 30 '20 23:11 martinezguillaume

Same issue, I've reproduced it on emulators running both above and below android 10+:

Device - Pixel 3 Emulator

  • Android Version - 11.0 (R) - API 30

Device - Pixel 2 Emulator

  • Android Version - 9.0 (Pie) - API 28

Are there any solutions for this issue?

jspizziri avatar Dec 03 '20 14:12 jspizziri

Same on Honor X8 (JSN-L21) Android 9.1.0

Could it be that it's related to the Software control bar android is rendering?

KDederichs avatar Dec 07 '20 13:12 KDederichs

the bottom returns 0 too in some full screen Android devices too, is there any progress about this? thanks alot.

{left: 0, bottom: 0, right: 0, top: 0}

terryatgithub avatar Oct 15 '21 07:10 terryatgithub

some issue

anatooly avatar Nov 25 '21 11:11 anatooly

Same issue with Pixel 2 Emulator

{"bottom": 0, "left": 0, "right": 0, "top": 0}

JeesubKim avatar Nov 28 '21 08:11 JeesubKim

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

initialWindowMetrics.insets

worked out for my use-case

fahad86 avatar Jan 21 '22 09:01 fahad86

any update on this issue ? Stil happen on 4.2.2

Aure77 avatar Mar 23 '22 15:03 Aure77

Same issue here. Redmi Note 8 Pro, Android 9.

ansmlc avatar May 03 '22 01:05 ansmlc

Android has a bunch of xml configs for this stuff. I know it's not documented here (PRs would be really apprecianted) - but you do have to configure them

jacobp100 avatar Jan 19 '23 16:01 jacobp100

@jacobp100 Please don't just close this issue without a detailed explanation of how to fix the issue.

The whole point of useSafeAreaInsets is that I don't need to worry about defining fallbacks for specific versions and types of devices. If the hook cannot guarantee that the correct insets are applied then there either needs to be proper documentation about its limitations or it needs to be fixed to work for all scenarios.

mstykow avatar Jan 25 '23 13:01 mstykow

We already have a ticket for that documentation, so that’s why this is closed

jacobp100 avatar Jan 25 '23 15:01 jacobp100

We already have a ticket for that documentation, so that’s why this is closed

Could you reference it here?

mstykow avatar Jan 25 '23 16:01 mstykow

https://github.com/th3rdwave/react-native-safe-area-context/issues/349

jacobp100 avatar Jan 25 '23 19:01 jacobp100

I'll leave this comment here and a reference to it on #153 (which refers to the same kind of issue) because I think someone could have had my very same problem, and it took a stupid amount of time to figure it out (for all the wrong reasons) so I wanted to share in case it could help.

EDIT 23/04/2023: I forgot to turn on syntax highlighting on a React code snippet, doing it right now.

tl;dr

If you're developing with Expo and didn't explicitly do anything that could affect the AndroidManifest.xml file, make sure you have <StatusBar style='auto' /> in your App component (the root of your project)! Expo already takes care of the most common XML configs for you, but if you don't add that component, useSafeAreaInsets() will say that the top inset is 0!

The actual story

1/7 - My setup I'm developing for both iOS and Android but I'm mainly testing on Android. I was developing my app with Expo, managed workflow (using Expo Go all the way from the start). A few days ago I compiled my own custom dev client for the first time, as I needed to add react-native-pdf as a dependency. Expo version is 48.0.11, React Native version is 0.71.6.

2/7 - The issue After compiling my custom dev client I found out that useSafeAreaInsets() was returning 0 for all sides. As a consequence, a component for calligraphic signatures that was styled using those values was partially off-screen. The strange fact is that in Expo Go it didn't happen. I double checked it: in Expo Go it just kept working, the issue was in the custom client only (more on that in section 7/7).

3/7 - Minimum reproducible example To check if the problem was related with React Navigation and being deep down the tree, I added another useSafeAreaInsets() call just inside the top-level <SafeAreaProvider> in my app. It wasn't: it happened even at the top level. So I created the smallest amount of code that reproduced the issue in my App.tsx (root) file:

import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';

export default function App() {
  return (
  <SafeAreaProvider>
    <InsideComponent />
  </SafeAreaProvider>
  )
}

function InsideComponent() {
  const insets = useSafeAreaInsets();
  console.log(insets);
  return <Text>Hello World</Text>;
}

The fact is that I have a lot of dependencies in my app, so I went a step further to be sure: I created a brand new Expo project (npx create-expo-app my-test), added react-native-safe-area-context as a dependency and put this snippet of code in the App.tsx file. It was still happening.

4/7 - The AndroidManifest and Expo I found this thread and I took for granted that I was missing something in my AndroidManifest.xml file. So I downloaded the react-native-safe-area-context repository, built the example app, ran the hooks example and verified whetheruseSafeAreaInsets() worked properly there. It did, so I just had to compare the AndroidManifest.xml from the example app with my test app, find out what I was missing and call it a day, right? Nope.

I found there weren't any real differences between my app and the example app - so I convinced myself the problem wasn't there.

Notice: What I'm claiming is that Expo automatically adds:

<activity
    android:name=".MainActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode">
    ...
</activity>

to the AndroidManifest.xml file, so in an Expo project the issue is not there.

5/7 - It's not the same code if you didn't use CTRL+C CTRL+V. Also, RTFM In despair, I started diving into the details of react-native-safe-area-context to see if I could tap into a few places and see where things were going wrong. I still had the feeling that I was doing something wrong, though.

Reading the HooksExample.tsx file, I noticed that I completely missed the existence of initialWindowMetrics - always remember to RTFM! So I imported that in my test app and guess what? In it, the top inset was 24! I just had to find where (and why) the information was being lost between there and the useSafeAreaInsets() call.

In src/SafeAreaContext.tsx I noticed that <SafeAreaProvider> took initialMetrics as a prop (a second voice in my head started screaming "RTFM"). I thought that maybe the example app in the library was using it and I wasn't - but no, the difference wasn't there. But there were other interesting variables used in <SafeAreaProvider>'s implementation:

const [insets, setInsets] = React.useState<EdgeInsets | null>(
    initialMetrics?.insets ?? initialSafeAreaInsets ?? parentInsets ?? null,
);

What are parentInsets? My <SafeAreaProvider> has no parents, maybe I forgot to wrap everything in... something and it was necessary? I didn't thoroughly RTFM, after all. So I went back to the example code:

<View style={{ marginTop: 0, flex: 1 }}>
  <SafeAreaProvider>
    <SimpleExampleScreen />
  </SafeAreaProvider>
</View>

See, there's a <View> there! My code didn't have that. I was so sure there were no differences between my code and the code in the example, yet here we are.

6/7 - Finally, the end Sadly, it wasn't that - but the revelation that I hadn't been careful enough in comparing my code with the example kicked me so hard that I started checking more thoroughly and, turns out, it was <StatusBar>'s fault. In the example, the <StatusBar> component is inside <SimpleExampleScreen>. In my snippet, there's no <StatusBar>.

7/7 - Takeaways and further investigations I removed <StatusBar> from my application a couple months ago and never thought about it since. I was on a rush and I recall deciding that it was the cause of some trouble (ironically, with the very same calligraphic signature component). Also, everything seemed to work just fine without it.

That is because in Expo Go the library still senses the correct insets when you start your app without the <StatusBar> component. If you have it at launch and delete it while the app is still going (letting Metro do its thing and updating the app), though, Expo Go shows you the truth - insets become 0.

I didn't dig further, but I have a theory: Expo Go probably has its own <StatusBar> and, when you add yours, it needs to do some magic to avoid issues - but then if you don't use it, it lies to you. I'll find some time to investigate this, cause it could be something worth pointing out to the people at Expo itself.

Apart from RTFM and you can't be sure that two snippets are the same if they're not the result of copy-pasting, there's one more take away here:

Do thorough testing even if you are in a rush, because it will backfire threefold.

Sure as hell my issue two months ago wasn't related to <StatusBar>, if today I find myself reintroducing it. I should have investigated better at that time, to save (quite a lot of) time to my future self.


(Also, don't use Expo Go and build your own client. It will teach you a lot, as a side benefit).

matthews314 avatar Apr 21 '23 17:04 matthews314