react-native-safe-area-context
react-native-safe-area-context copied to clipboard
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?
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 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?
Do you have an example of an android device or simulator where the issue happens?
Do you have an example of an android device or simulator where the issue happens?
It happens on my Galaxy S10
Same here...Samsung S10e
Same on S10+
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);
}
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.
Same on S10+
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?
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?
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}
some issue
Same issue with Pixel 2 Emulator
{"bottom": 0, "left": 0, "right": 0, "top": 0}
using
import {initialWindowMetrics} from 'react-native-safe-area-context';
initialWindowMetrics.insets
worked out for my use-case
any update on this issue ? Stil happen on 4.2.2
Same issue here. Redmi Note 8 Pro, Android 9.
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 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.
We already have a ticket for that documentation, so that’s why this is closed
We already have a ticket for that documentation, so that’s why this is closed
Could you reference it here?
https://github.com/th3rdwave/react-native-safe-area-context/issues/349
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).