th3rdwave / react-native-safe-area-context

A flexible way to handle safe area insets in JS. Also works on Android and Web!
MIT License
2.17k stars 200 forks source link

useSafeAreaInsets all return 0 for values #153

Closed douglasrcjames closed 4 years ago

douglasrcjames commented 4 years ago

I am trying to fix an issue while using the tab navigator and/or Safe Area where my keyboardAvoidingView is covering the message input box on the chat screen when the keyboard is brought up. Not sure what the culprit is exactly, but I am pretty sure it's the safeview padding on top and/or bottom, which I have learned are the "insets". I am trying to use useSafeAreaInsets, but all the values return 0! (See in CustomKeyboardAvoidingView) I want to use those insets to add to the keyboardVerticalOffset param so the avoiding view works properly. I tried setting the keyboardVerticalOffset to a flat value, but it seems to be different depending on the device, aka the inset padding values would make sense. I like the style of the tab bar right now, so I'd like to keep it with an increased height and padding and font size, but maybe I am doing it wrong with react native navigation? Regardless, the insets should be returning a value.

I tried using the solution outlined in this issue: https://github.com/th3rdwave/react-native-safe-area-context/issues/54

Here is my App.js code recreating the issue:

import React, { Component, useEffect, useState } from 'react';
import { View, KeyboardAvoidingView, TextInput, Text, Platform, TouchableWithoutFeedback, Keyboard, ActivityIndicator, SafeAreaView, ScrollView, Button, StatusBar   } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator, useHeaderHeight } from '@react-navigation/stack';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import { useSafeAreaInsets, useSafeAreaFrame } from 'react-native-safe-area-context';
import { Dimensions } from 'react-native';

const Stack = createStackNavigator();

function TicketStack() {
  return (
    <Stack.Navigator
    screenOptions={{
      headerStyle: { 
        backgroundColor: "dodgerblue",
        elevation: 0, // remove shadow on Android
        shadowOpacity: 0, // remove shadow on iOS
      },
      headerTintColor: "#fff",
      headerTitleStyle: {
        fontWeight: "900",
        fontSize: 26,
      },

    }}>
      <Stack.Screen name="Ticket">
        {(props) => <TicketScreen {...props} />}
      </Stack.Screen>
      <Stack.Screen name="Chat">
        {(props) => <ChatScreen {...props} />}
      </Stack.Screen>
    </Stack.Navigator>
  );
}

function HomeScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text><FontAwesome5 name={"home"} size={20} color={"dodgerblue"} /> Home screen!</Text>
    </View>
  );
}

function TicketScreen(props){
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Ticket screen :)!</Text>
      <Button title="Go to Chat" onPress={() => props.navigation.navigate('Chat')} />
    </View>
  );
}

function CustomKeyboardAvoidingView({ children, style }) {
    const headerHeight = useHeaderHeight();
    const insets = useSafeAreaInsets();
    const [bottomPadding, setBottomPadding] = useState(insets.bottom)
    const [topPadding, setTopPadding] = useState(insets.top)
    // const frame = useSafeAreaFrame();
    // const windowHeight = Dimensions.get('window').height  
    useEffect(() => {
      setBottomPadding(insets.bottom)
      setTopPadding(insets.top)

      console.log("insets.top: " + topPadding)
      console.log("insets.bottom: " + bottomPadding)
    }, [insets.bottom, insets.top])

    // console.log("frame.height: " + frame.height)
    // console.log("windowHeight: " + windowHeight)
    // const safeAreaHeight = windowHeight - frame.height
    // console.log("safeAreaHeight: " + safeAreaHeight)
    // safeAreaHeight is too much, needs to just be bottom or top padding from safearea
    return (
        <KeyboardAvoidingView
            style={style}
            behavior={Platform.OS == "ios" ? "padding" : "height"}
            keyboardVerticalOffset={headerHeight + bottomPadding + StatusBar.currentHeight}
        >
            {children}
        </KeyboardAvoidingView>
    );
}

function ChatScreen(){
  return(

      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <CustomKeyboardAvoidingView style={{backgroundColor: "#fff", flex: 1, flexDirection: "column", justifyContent: "space-between" }}>
          <View style={{backgroundColor: "dodgerblue", paddingVertical: 15}}>
              <View style={{ margin: 10, marginBottom: 15}}>
                  <ActivityIndicator size="large" style={{marginBottom: 10}}/>
                  <Text>Waiting for more info here....</Text>
              </View>
          </View>

          <ScrollView style={{backgroundColor: "tomato", paddingVertical: 15}}>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
              <Text>Chat messages</Text>
          </ScrollView>

          <View style={{backgroundColor: "yellow", paddingVertical: 15}}>
              <TextInput placeholder="Type your message here..." />
          </View>
        </CustomKeyboardAvoidingView>
      </TouchableWithoutFeedback>

  )
}

const Tab = createBottomTabNavigator();

// TODO:
// - removing safeareaview makes tabs squished and icon nearly invisible, but chat message input fields avoids keyboard properly (squished can be fixed by removing tabBarOptions)
// - having safeareaview makes tabs look good, but chat message input field is hidden by keyboard
// - safeareainsets? why are they 0?
// - subtract height of safe area from full screen height to get the padding?
export default class App extends Component {
  render(){
    return (

        <NavigationContainer>
          <Tab.Navigator
            screenOptions={({ route }) => ({
              tabBarIcon: ({ color, size }) => {
                let iconName;

                if (route.name === 'Home') {
                  iconName = 'home';
                } else if (route.name === 'Ticket') {
                  iconName = 'question';
                }
                return <FontAwesome5 name={iconName} size={size} color={color} />;
              },
            })}
            tabBarOptions={{
              style: {
                height: 70
              }, 
              activeTintColor: "#fff",
              inactiveTintColor: "dodgerblue",
              inactiveBackgroundColor: "#fff",
              activeBackgroundColor: "dodgerblue",                               
              tabStyle: {
                paddingTop: 10,
                paddingBottom: 10
              },
              labelStyle: {
                fontSize: 14
              },
            }}>
            <Tab.Screen name="Home">
              {(props) => <HomeScreen {...props} />}
            </Tab.Screen>
            <Tab.Screen name="Ticket">
              {(props) => <TicketStack {...props} />}
            </Tab.Screen>
          </Tab.Navigator>
        </NavigationContainer>

    );
  }
}

package.json

{
  "name": "ReactNativeTest",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "@react-native-community/masked-view": "^0.1.10",
    "@react-navigation/bottom-tabs": "^5.9.2",
    "@react-navigation/native": "^5.7.6",
    "@react-navigation/stack": "^5.9.3",
    "react": "16.13.1",
    "react-native": "0.63.3",
    "react-native-gesture-handler": "^1.8.0",
    "react-native-reanimated": "^1.13.1",
    "react-native-safe-area-context": "^3.1.8",
    "react-native-screens": "^2.11.0",
    "react-native-vector-icons": "^7.1.0"
  },
  "devDependencies": {
    "@babel/core": "7.11.6",
    "@babel/runtime": "7.11.2",
    "@react-native-community/eslint-config": "1.1.0",
    "babel-jest": "25.5.1",
    "eslint": "6.8.0",
    "jest": "25.5.4",
    "metro-react-native-babel-preset": "0.59.0",
    "react-test-renderer": "16.13.1"
  },
  "jest": {
    "preset": "react-native"
  }
}
kaushil111 commented 4 years ago

Same for me. Looking into this

kaushil111 commented 4 years ago

I am having the same issue (insets are 0) but nothing stands out for me which is different from the implementation in the example app. The example app gives my ~42 as top and bottom insets on my android phone but is always 0 in my project. Upon deeper investigation these are always 0: https://github.com/th3rdwave/react-native-safe-area-context/blob/1628d2e3875831d39423e82c4012ef5b956dce7e/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaUtils.java#L57

windowInsets.top = Math.max(windowInsets.top - visibleRect.top, 0);
windowInsets.left = Math.max(windowInsets.left - visibleRect.left, 0);
windowInsets.bottom = Math.max(visibleRect.top + view.getHeight() + windowInsets.bottom - windowHeight, 0);
windowInsets.right = Math.max(visibleRect.left + view.getWidth() + windowInsets.right - windowWidth, 0);
douglasrcjames commented 4 years ago

I am mainly testing on iOS right now, but I am sure I will need it to work on Android side as well. Not sure what you mean by "having the same issue but nothing stands out".

douglasrcjames commented 4 years ago

I have posted a better-formatted question on StackOverflow and will report back if I find a solution: https://stackoverflow.com/questions/64454207/keyboardavoidingview-react-navigation-safe-area-view-not-working

alexandersandberg commented 4 years ago

Have you tried wrapping your App in SafeAreaProvider?

export default class App extends Component {
  render(){
    return (
      <SafeAreaProvider>
        ...
      </SafeAreaProvider>
    );
  }
}
kaushil111 commented 4 years ago

Yep I have a provider at the top level

douglasrcjames commented 4 years ago

@alexandersandberg Wrapping the app in SafeAreaProvider fixes the issue and the insets now properly return values. Thanks! I am still having an issue with React Navigation working nicely, but that's not relevant to this thread.

Seamus1989 commented 3 years ago

still getting this despite use of <SafeAreaProvider />

rgouzal commented 3 years ago

@alexandersandberg Same here.

Luchanso commented 2 years ago

I rewrote CustomKeyboardAvoidingView little bit (work for me):

function CustomKeyboardAvoidingView({children, style}: any) {
  const insets = useSafeAreaInsets();
  const headerHeight = useHeaderHeight();

  return (
    <KeyboardAvoidingView
      style={StyleSheet.compose(style, {marginBottom: insets.bottom})}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      enabled={true}
      keyboardVerticalOffset={headerHeight + insets.bottom}>
      {children}
    </KeyboardAvoidingView>
  );
}

usage: image

lvdiev commented 2 years ago
useSafeAreaInsets
useHeaderHeight

where are useSafeAreaInsets and useHeaderHeight defined?

Luchanso commented 2 years ago
useSafeAreaInsets
useHeaderHeight

where are useSafeAreaInsets and useHeaderHeight defined?

import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {useHeaderHeight} from '@react-navigation/elements';
matthews314 commented 1 year ago

If you're developing with Expo and didn't explicitly do anything that could affect the AndroidManifest.xml file, make sure you have 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!

More details here: https://github.com/th3rdwave/react-native-safe-area-context/issues/124#issuecomment-1518126533