react-native-safe-area-context: useSafeAreaInsets on Android returns 0 for top
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?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 12
- Comments: 23
using
import {initialWindowMetrics} from 'react-native-safe-area-context';
initialWindowMetrics.insets
worked out for my use-case
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: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
), addedreact-native-safe-area-context
as a dependency and put this snippet of code in theApp.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 thereact-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 theAndroidManifest.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:
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 ofinitialWindowMetrics
- 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 theuseSafeAreaInsets()
call.In
src/SafeAreaContext.tsx
I noticed that<SafeAreaProvider>
tookinitialMetrics
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: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: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).
@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.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 toimport {StatusBar} from 'react-native';
) to the top inset if the app is running on Android.Hope that helps.
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:
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?
Same issue, I’ve reproduced it on emulators running both above and below android 10+:
Device - Pixel 3 Emulator
Device - Pixel 2 Emulator
Are there any solutions for this issue?
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 affectinitialWindowMetrics.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.