KeyboardAvoidingView not Working Properly

I am trying to use the KeyboardAvoidingView with behavior="padding".

For some reason, when I'm trying to enter any text in TextInput, there's a space below the TextInput. Attached is a picture of what is happening as well as the code. Any chance anyone has any idea whats happening here?

  render() {
    return (

      <KeyboardAvoidingView  style={{ flex: 1}}  behavior="padding">
      < View
          style={{
            flex: 1,
           
          backgroundColor: "#FFFFFF",
         
        }}
      >
        
        <ScrollView
          contentContainerStyle={{ justifyContent: "flex-end", flex: 1 }}>
                <ChatInfo />
              </ScrollView>

        
          <View style={styles.container}>
          <TextInput
            style={styles.input}
            underlineColorAndroid="transparent"
            autoCapitalize="none"
            onChangeText={text => this.setState({ text: text })}
            value={this.state.text}
          />

          <TouchableOpacity
            style={styles.submitButton}
            onPress={this.submitName}
          >
            <Text style={styles.submitButtonText}> SEND </Text>
          </TouchableOpacity>
        </View>
       
      </ View>
      </KeyboardAvoidingView>
    );
  }
}

export default connect()(ChatScreen);

const styles = StyleSheet.create({
  input: {
    margin: 2,
    paddingLeft: 15,
    flex: 1,
    height: 40,
    padding: 10,
    fontSize: 14,
    fontWeight: "400"
  },

      container: {
        borderTopWidth: 1,
        minWidth: "100%",
        borderColor: "#cccccc",
        height: 44,
        flexDirection: "row",
        justifyContent: "space-between",
        backgroundColor: "#fff"
        
      },

  submitButtonText: {
    color: "#0a9ffc",
    fontSize: 14,
    fontWeight: "500"
  },

  submitButton: {
    backgroundColor: "#fff",
    padding: 10,
    margin: 2,
    height: 40,
    alignItems: "center",
    justifyContent: "center"
  }
});

Solution 1

If you are using react-navigation, this is affected by the header of the react-navigation. The height of the header is vary on different mobile screen. So you have to get the height of the header and pass into the keyboardVerticalOffset props.

import { Header } from 'react-navigation-stack';

<KeyboardAvoidingView
  keyboardVerticalOffset = {Header.HEIGHT + 20} // adjust the value here if you need more padding
  style = {{ flex: 1 }}
  behavior = "padding" >

  <ScrollView>
    <TextInput/>
    <TextInput/>
    <TextInput/>
    <TextInput/>
    <TextInput/>
    <TextInput/>
  </ScrollView> 

</KeyboardAvoidingView>

Solution 2

This is a known issue with KeyboardAvoidingView and Android. There are multiple ways to address this issue.

React Native documentation says:

Android may behave better when given no behavior prop at all, whereas iOS is the opposite.

So, if you are working only with Android you may remove behavior prop and it should work straight away. For best results add android:windowSoftInputMode="adjustResize" to your Manifest.

Alternatively you can give an offset value that works for you something like this: KeyboardAvoidingView keyboardVerticalOffset={-500} behavior="padding"

For ios do the same thing conditionally:

behavior= {(Platform.OS === 'ios')? "padding" : null}

keyboardVerticalOffset={Platform.select({ios: 0, android: 500})}

Solution 3

WARNING

This appears to be only a partial solution, although it works initially, if the android phone is locked on the screen with the keyboard avoiding layout, when you unlock you end up with the extra padding above the keyboard again.

tl;dr

Remove android:windowSoftInputMode="adjustResize" from the AndroidManifest.xml

Before

...
<activity
  android:name=".MainActivity"
  android:label="@string/app_name"  
  android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
  android:windowSoftInputMode="adjustResize"
  >
...

After

...
<activity
  android:name=".MainActivity"
  android:label="@string/app_name"  
  android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
  >
...

Why

If I understand the issue correctly, I have been dealing with the same thing. By having android:windowSoftInputMode="adjustResize" in the manifest, the android system will try to do the same job as the KeyboardAvoidingView. This results in extra spacing being added above the keyboard on Android only.

If working on both platforms you are going to have to deal with this on iOS every time you are working with keyboard input, so best to remove the android specific behaviour by android:windowSoftInputMode="adjustResize" from the manifest and using the KeyboardAvoidingView every time.

Solution 4

The KeyboardAvoidingView must be a ScrollView child, not the other way around. This way it behaves normal(normal for what purpose I am using it). Try it and let me know how it went.

<ScrollView>
    <KeyboardAvoidingView styles={styles.container} behavior='padding'>

    </KeyboardAvoidingView>
</ScrollView>

Solution 5

For anyone coming here in 2021, a few things for an updated answer:

  • useHeaderHeight is no longer exported by @react-navigation/stack, it is in @react-navigation/elements.

  • React Native also now recommends setting the behavior prop for both iOS and Android. So the full solution would be:

import { useHeaderHeight } from '@react-navigation/elements';

export const MyComponent = () => {
  const headerHeight = useHeaderHeight();
  return (
      <KeyboardAvoidingView
        keyboardVerticalOffset={headerHeight}
        style={style.container}
        behavior={Platform.OS === "ios" ? "padding" : "height"}
      >
      {/* rest of your component */}
      </KeyboardAvoidingView>
    );
}

I didn't have to add any additional value to headerHeight in keyboardVerticalOffset, and it's working great on iOS and Android.

EDIT: The problem now goes deeper, as KeyboardAvoidingView does not support all types of Android keyboards. The solution must rely on opening the keyboard to a specific element then (for example a text input). This custom component would look like this:

import React, { PropsWithChildren, useEffect, useState } from 'react';
import { Platform, Animated, Dimensions, Keyboard, KeyboardAvoidingView, StyleSheet, TextInput } from 'react-native';
import {useHeaderHeight} from '@react-navigation/elements';
import { useKeyboard } from '@react-native-community/hooks';

export default function KeyboardShift (props: PropsWithChildren<{}>) {
  const [shift, setShift] = useState(new Animated.Value(0))
  const keyboard = useKeyboard()

  // On mount, add keyboard show and hide listeners
  // On unmount, remove them
  useEffect(() => {
    Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow);
    Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide);
    return () => {
      Keyboard.removeAllListeners('keyboardDidShow');
      Keyboard.removeAllListeners('keyboardDidHide');
    }
  }, [])

  const handleKeyboardDidShow = () => {
    const { height: windowHeight } = Dimensions.get('window');
    const keyboardHeight = keyboard.keyboardHeight;
    const currentlyFocusedInputRef = TextInput.State.currentlyFocusedInput();
    currentlyFocusedInputRef.measure((x, y, width, height, pageX, pageY) => {
      const fieldHeight = height;
      const fieldTop = pageY;
      const gap = (windowHeight - keyboardHeight) - (fieldTop + fieldHeight);
      if (gap >= 0) {
        return;
      }
      Animated.timing(
        shift,
        {
          toValue: gap,
          duration: 1000,
          useNativeDriver: true,
        }
      ).start();
    })
  }

  const handleKeyboardDidHide = () => {
    Animated.timing(
      shift,
      {
        toValue: 0,
        duration: 1000,
        useNativeDriver: true,
      }
    ).start();
  }

  const { children } = props;

  // Android: we need an animated view since the keyboard style can vary widely
  // And React Native's KeyboardAvoidingView isn't always reliable
  if (Platform.OS === 'android') {
    return (
      <Animated.View style={[styles.container, { transform: [{translateY: shift}] }]}>
        {children}
      </Animated.View>
    );
  }

  // iOS: React Native's KeyboardAvoidingView with header offset and 
  // behavior 'padding' works fine on all ios devices (and keyboard types)
  const headerHeight = useHeaderHeight();
  return (
    <KeyboardAvoidingView
      keyboardVerticalOffset={headerHeight}
      style={styles.container}
      behavior={'padding'}>
      {children}
    </KeyboardAvoidingView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

Yes, it's unfortunately long-winded, but it gets the job done for all types of phones on both iOS and Android.

Read more about it here.

SECOND EDIT: As of April 22nd, 2022, I've replaced the complex solution above the following:

Step 1:

behavior={Platform.OS === "ios" ? "padding" : undefined}

Step 2:

Be sure to REMOVE

android:windowSoftInputMode="adjustPan"

(Or any windowSoftInputMode for that matter) from your AndroidManifest.xml file.

So far this solution has been working as well (and is way less code - always better, right?)

Solution 6

I think this is because the behavior props value, so I think adding this line in the keyboardavoidview will help

<KeyboardAvoidingView
    style = {{ flex: 1 }}
    behavior={Platform.OS === "ios" ? "padding" : null}>
</KeyboardAvoidingView>

Solution 7

The main issue with KeyboardAvoidingView is that; the understanding of how it works is missing.

I found below link very helpful

https://medium.com/@nickyang0501/keyboardavoidingview-not-working-properly-c413c0a200d4

  1. First thing, use of flex:1 in KeyboardAvoidingView and behavior: 'padding'
  2. Next is use of flex:1 in "MainView" which needs to go inside KeyboardAvoidingView
  3. Last is adding justifyContent: "flex-end" to ""MainView""

Hope it helps

Solution 8

For React Native Navigation v5, you can use the following:

import { useHeaderHeight } from '@react-navigation/stack';

...

const Component = () => (

<KeyboardAvoidingView
  keyboardVerticalOffset={ useHeaderHeight() } // <-- for v5
  behavior="padding"
  style={{ flex: 1 }}
>
  <TextInput
    style={{ height: 30, width: "100%, borderWidth: 1 }}
  />
</KeyboardAvoidingView

)

https://reactnavigation.org/docs/stack-navigator#headertransparent

Solution 9

Many answers here have shown a conditional behavior prop value. Like this.

// incorrect 
<KeyboardAvoidingView
    style = {{ flex: 1 }}
    behavior={Platform.OS === "ios" ? "padding" : null}>
</KeyboardAvoidingView>

But this sets the behavior prop to null on Android.

The documentation says...

Android and iOS both interact with this prop differently.
Android may behave better when given no behavior prop at all, whereas iOS is the opposite.

Conditional spreading the behavior prop provides an exact solution.
It adds the prop on iOS and leaves it out on Android.

// correct 
<KeyboardAvoidingView
    style = {{ flex: 1 }}
    {...(Platform.OS === 'ios' && { behavior: 'padding' })}
</KeyboardAvoidingView>

And here's a solution if using styled-components that doesn't use an unnecessary KeyboardAvoidingView on Android.

import { KeyboardAvoidingView as Kav, Platform, View } from 'react-native';
import styled from 'styled-components/native';

// If ios we change the component type and, via the `attrs` method, add a behavior prop. This
// approach leaves Android alone. Because it already works.
export const ScreenContainer = styled(Platform.OS === 'ios' ? Kav : View).attrs({
  behavior: Platform.OS === 'ios' && 'padding',
})`
  flex: 1;
`;

Solution 10

<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : null} 
style={{flex: 1 }}>

In the above snippet, flex is set to 1, which renders the text Input field just above the keyboard by default even when the keyboard isn't opened. And, when the keyboard pops up, the input field is further pushed up by a factor of keyboard height's offset.

Setting flex to 0 shall fix the issue, as it did in my case.

 <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : null} 
  style={{flex: 0 }}>
  <View>
   ...
  </View>
 </KeyboardAvoidingView>

Solution 11

UPDATE: 2021(October)

If you are using react-navigation(currently, I'm using v6), import useHeaderHeight hook(require additional dependencies)

If you want to use ScrollView with KeyboardAvoidingView, you have to wrap ScrollView inside KeyboardAvoidingView(see example below)

NOTE: If your screen have bottomTabsNavigation, be sure to DISABLE on iOS platform tabBarHideOnKeyboard: Platform.OS !== 'ios'

   const headerHeight = useHeaderHeight();
    
      <KeyboardAvoidingView 
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={{flex: 1}}
        keyboardVerticalOffset={headerHeight}>
         <ScrollView style={{flex: 1}} contentContainerStyle={{flexWrap: 1}}>
             ...content
         </ScrollView>
    
      </KeyboardAvoidingView/>

Solution 12

<KeyboardAvoidingView styles={styles.container} behavior = 'padding'  enabled>
   <ScrollView>

        <View>
          ....
        </View>

   </ScrollView>
</KeyboardAvoidingView>

Solution 13

I think the best approach is to create a HOC for this, in addition ,by using getBottomSpace from react-native-iphone-x-helper you can solve overlapping issue for IPhone X and..

 import React, { ComponentType, ReactNode } from 'react';
    import { Platform, KeyboardAvoidingView, View, Pressable, Keyboard } from 
    'react-native';
    import { getBottomSpace } from 'react-native-iphone-x-helper';

    interface IProps {
    children: ReactNode;
    }

    const KeyboardAvoidingViewHoc = (Component: ComponentType) => {
    return ({ children, ...props }: IProps) => {
    return (
      <KeyboardAvoidingView  {...props} keyboardVerticalOffset= 
   {getBottomSpace()} behavior= {Platform.OS === 'ios' ? 'padding' : undefined}>
        <Pressable onPress={Keyboard.dismiss}>
          <Component {...props}>{children}</Component>
        </Pressable>
      </KeyboardAvoidingView>
    );
  };
};

export const AvoidKeyboardAvoidingViewHoc = KeyboardAvoidingViewHoc(View);

Solution 14

After applying ScrollView the problem was solved for me.

Solution 15

A handy alternative when wanting to avoid the keyboard with a ScrollView is the popular react-native-keyboard-aware-scroll-view package:

https://github.com/APSL/react-native-keyboard-aware-scroll-view

import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'

<KeyboardAwareScrollView>
  <View>
    <TextInput />
  </View>
</KeyboardAwareScrollView>

Solution 16

My issue was with the keyboardHidesTabBar option. The following setup worked for me:

const AppBottomTabNavigator = createBottomTabNavigator(
  {
    ...
  },
  {
    tabBarOptions: {
      keyboardHidesTabBar: Platform.OS !== 'ios',
    },
  },
);

Component:

import React from 'react';
import {
  Keyboard,
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
  Text,
  TextInput,
  TouchableWithoutFeedback,
  View,
} from 'react-native';
import { Header } from 'react-navigation-stack';

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  center: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  textInput: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
  },
});

const MyScreen = () => {
  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : null}
      keyboardVerticalOffset={Header.HEIGHT}
      style={styles.container}
    >
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <View style={styles.container}>
          <View style={[styles.container, styles.center]}>
            <Text>Hello!</Text>
          </View>
          <TextInput style={styles.textInput} placeholder="Message" />
        </View>
      </TouchableWithoutFeedback>
    </KeyboardAvoidingView>
  );
};

export default MyScreen;

Solution 17

My problem was not checking this platform type

adding the code below to KeyboardAvoidView fixed it for me

behavior={Platform.OS === "ios" ? "padding" : 'height'}

Solution 18

Although Its not an right answer but there is very popular library for solving this type of issues is there called where it can use scrollview with the sense of keyboard area available.You can go through the below link https://www.npmjs.com/package/react-native-keyboard-aware-scrollview

Solution 19

I had a similar issue because I'm using @react-navigation with bottom tabs.

Starting with "@react-navigation/bottom-tabs": "^5.11.2" you can get the height of the bottom tab bar with one of the following two ways (API reference):

import { BottomTabBarHeightContext } from '@react-navigation/bottom-tabs';

// ...

<BottomTabBarHeightContext.Consumer>
 {tabBarHeight => (
   /* render something */
 )}
</BottomTabBarHeightContext.Consumer>

or

import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';

// ...

const tabBarHeight = useBottomTabBarHeight();

and then you set it as offset to your KeyboardAvoidingView:

<KeyboardAvoidingView keyboardVerticalOffset={tabBarHeight} behavior={Platform.OS === "ios" ? "padding" : null} style={styles.container}>
  // ...
</KeyboardAvoidingView>

Solution 20

Keep in mind is always your top Parent with flex:1 then the child is then you text input container if you use this method it work always its test method.

<KeyboardAvoidingView style={{  flex: 1 }}
    behavior={Platform.OS === "ios" ? "position" : null} enabled>
      <ScrollView>
        <View>
          <View >
            <Text maxFontSizeMultiplier={1.5} >
              Sign in to your account{" "}
            </Text>
             <View
                behavior="padding"
                enabled
              >
                <TextInput
                  placeholder="Email address"
                  placeholderTextColor={Colors.grey}
                  style={styles.textInput}
                  onChangeText={(e) => setEmail(e.trim())}
                  autoCapitalize="none"
                  returnKeyType={"done"}
                />
              </View>
        </View>
      </ScrollView>
</KeyboardAvoidingView>

Solution 21

import { HeaderHeightContext } from "react-navigation-stack"; import { ..., KeyboardAvoidingView, } from "react-native";

<HeaderHeightContext.Consumer>
  {(headerHeight) => (
    <KeyboardAvoidingView
      {...(Platform.OS === "ios" && { 
            behavior: "padding",
            keyboardVerticalOffset: headerHeight
      })}
       style={{ flex: 1 }}>

 code is here 

</KeyboardAvoidingView>

)} </HeaderHeightContext.Consumer>

Solution 22

 <KeyboardAvoidingView style={styles.keyboardcontainer} behavior="padding"
       keyboardVerticalOffset={Platform.select({ios :120, android : 500})}
       enabled>

    <View  style={{flex: 1 }}>
        // Your Code 
    </View>

</KeyboardAvoidingView>