react-native: Bug in KeyboardAvoidingView on Android

Description

Hi, there is a problem with KeyboardAvoidingView on Android which is best presented by the screenshots below:

Without keyboard:

android_no_keyboard

With keyboard:

android_with_keyboard

It just adds too much padding to the view. On iOS it works correctly. I’ve also tried different behaviors: height, position but with the same effect.

Reproduction

Here is my JSX:

<KeyboardAvoidingView behavior="padding" style={{
  flex: 1
}}>
  <View style={{
    flex: 0,
    height: 50,
    backgroundColor: 'skyblue'
  }}>
    <Text>Header</Text>
  </View>
  <ScrollView style={{
    flex: 2,
    padding: 10
  }}>
    <Text>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</Text>
  </ScrollView>
  <View style={{
    flex: 0,
    height: 50,
    backgroundColor: 'orange'
  }}>
    <TextInput
      style={{
        marginLeft: 10,
        height: 50
      }}
      ref="input"
      placeholder="Your message here..."
      returnKeyType="send"
      underlineColorAndroid="transparent"
    />
  </View>
</KeyboardAvoidingView>

Additional Information

  • React Native version: 0.39.2
  • Platform: Android
  • Operating System: MacOS

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 97
  • Comments: 77 (3 by maintainers)

Commits related to this issue

Most upvoted comments

Hey Guys

My solution is very simple. KeyboardAvoidingView is englobing my <Router> and writen just one time in my application See below:

import {KeyboardAvoidingView, Platform} from "react-native";

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

I got bitten by this on android. My workaround was to edit AndroidManifest.xml:

    <activity
        android:windowSoftInputMode="adjustResize"

And only use KeyboardAvoidingView on iOS. YMMV. Would of course be very nice if KeyboardAvoidingView worked perfectly on both platforms 😃.

The only solution I found for now is to define keyboardVerticalOffset and put everything in a ScrollView keyboardVerticalOffset={-500}

KeyboardAvoidingView keyboardVerticalOffset={-500} behavior="padding"
    ScrollView
        TextInput

For me, if I don’t specified behavior props on Android, it works !

For me it was solved using const offset = (Platform.OS === 'android') ? -200 : 0; and then keyboardVerticalOffset={offset} to adjust the keyboard` spacing.

This is fixed/explained in the upcoming release (v0.42) of RN https://github.com/facebook/react-native/commit/e3d4ace3ae46e3e4046e1ec5a201b92033deb24a

I’m on 0.53.0… I hate bots.

Thanks for posting this! It looks like you may not be using the latest version of React Native, v0.53.0, released on January 2018. Can you make sure this issue can still be reproduced in the latest version?

I am going to close this, but please feel free to open a new issue if you are able to confirm that this is still a problem in v0.53.0 or newer.

How to ContributeWhat to Expect from Maintainers

@eflath I’ve got it working quite nicely with a react navigation TabNavigator:

  1. in AndroidManifest, set android:windowSoftInputMode="adjustPan"
  2. my TabNavigator is the first screen in a containing StackNavigator, and the TabNavigator’s tabs are each StackNavigators. A screen in one of the TabNavigator’s tab stacks has KeyboardAvoidingView
  3. This is the outer view for the screen:
<KeyboardAvoidingView
        behavior={'padding'}
        keyboardVerticalOffset={Platform.select({ios: 0, android: 25})}
        style={{flex: 1}}>

I don’t yet understand why 25 works. My tab bar is 50. I’m half anticipating finding out that it shouldn’t be 25 and should be something else, but atm it seems to work.

@djw27 that fix does not solve a problem 😕

For me works by passing null to android:

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

Needs to be reopened as this issue is present in 0.53.0

As I said in https://github.com/facebook/react-native/issues/11681#issuecomment-360712806, we cannot keep on using black magic platform-dependant tricks to develop applications in this framework which is supposed to be platform seamless, this issue #2852 has to be re-opened.

If opening keyboard with the setting android:windowSoftInputMode="adjustNone" fires keyboard events, then, KeyboardAvoidingView components could catch them and resize any View regardless of the platform and without keeping glitch exploits in the code. This is the proper way of handling this issue.

Same issue here, keyboard avoiding view seems a bit unpredictable - probably the biggest pain point faced with core react native right now. Setting null behaviour for android and an arbitrary padding for ios has given me the result I wanted in the end

I’ve noticed that not specifying behavior will get different results from explicitly setting any one of the three behaviors mentioned on the doc page.

OK. Please, stop commenting Expo code “solutions” since the problem comes only from native projects. The problem is that on iOS the keyboard “hovers” the app so you need a KeyboardAvoidingView to resize the viewport, BUT in Android, the android:windowSoftInputMode="adjustResize" property does it automatically. Using android:windowSoftInputMode="adjustPan" only pans the viewport to avoid the kayboard and android:windowSoftInputMode="adjustNone" does nothing AND does not fire Keyboard events in opposite of the previous two, which is problematic. That’s the reason you disable the KeyboardAvoidingView in Android but not on iOS using a ternary like in https://github.com/facebook/react-native/issues/11681#issuecomment-320236475 and use android:windowSoftInputMode="adjustResize" for the moment.

EDIT: There is an issue #2852 (also #9640) for that specific problem that would resolve everything, it needs to be re-opened.

I ended up using <KeyboardAvoidingView keyboardVerticalOffset={-200}> and changing in the AndroidManifext.xml android:windowSoftInputMode="adjustPan"

It is by no means a fix, but have you tried making your own KeyboardAvoidingView? I had to create a chatview for a project of mine and it works like a charm, maybe this will help you:

import React, { Component } from 'react';
import {
    View,
    StyleSheet,
    TextInput,
    KeyboardAvoidingView,
    Keyboard,
    Dimensions,
    Platform,
} from 'react-native';

const { height: windowHeight } = Dimensions.get('window');

export default class ChatView extends Component {

    state = {
        keyboardOffset: 0,
    };

    constructor(props) {
        super(props);

        this.getKeyboardOffsetStyle = this.getKeyboardOffsetStyle.bind(this);
        this.handleKeyboardShow = this.handleKeyboardShow.bind(this);
        this.handleKeyboardHide = this.handleKeyboardHide.bind(this);

        Keyboard.addListener('keyboardDidShow', this.handleKeyboardShow);
        Keyboard.addListener('keyboardWillShow', this.handleKeyboardShow);
        Keyboard.addListener('keyboardWillHide', this.handleKeyboardHide);
        Keyboard.addListener('keyboardDidHide', this.handleKeyboardHide);
    }

    handleKeyboardShow({endCoordinates: { height }}) {
        this.setState({keyboardOffset: height});
    }
    
    handleKeyboardHide() {
        this.setState({keyboardOffset: 0});
    }

    getKeyboardOffsetStyle() {
        const { keyboardOffset } = this.state;
        return Platform.select({
            ios: () => ({ paddingBottom: keyboardOffset }),
            android: () => ({ height: windowHeight - keyboardOffset }),
        })();
    }

    render() {
        const { children } = this.props;
        const { style } = this;

        return (
            <View style={[style.container, this.getKeyboardOffsetStyle()]}>
                <View style={[style.inner]}>
                    {children}
                </View>
                <TextInput style={[style.input]} />
            </View>
        );
    }

    style = StyleSheet.create({
        container: {
            flex: 1,
            backgroundColor: '#0f0',
            padding: 0,
            width: '100%'
        },
        inner: {
            flexGrow: 1,
            backgroundColor: '#00f'
        },
        input: {
            height: 40,
            width: 200,
            borderColor: 'gray',
            borderWidth: 1,
            backgroundColor: '#f0f',
            width: '100%'
        },
    });
};

Of course you will still need to implement Keyboard.removeListener on componentWillUnmount.

note the:

getKeyboardOffsetStyle() {
    const { keyboardOffset } = this.state;
    return Platform.select({
        ios: () => ({ paddingBottom: keyboardOffset }),
        android: () => ({ height: height - keyboardOffset }),
    })();
}

Funny enough, when I tried to use paddingBottom for Android, I recreated the same bug as the react-native KeyboardAvoidingView that @jagi adresses. I think this has something to do with the fact that android:windowSoftInputMode in AndroidManifest.xml is set to "adjustResize" by default now. e3d4ace3ae46e3e4046e1ec5a201b92033deb24a

For those wondering, there is an “enabled” prop that can be used to disable the component on specific platform

<KeyboardAvoidingView ... enabled={Platform.OS === 'ios'} />

Really hope this gets prioritised - keyboard handling is always such a nightmare

Using behavior={(Platform.OS === 'ios') ? 'padding' : null} as commented above solved the problem for me.

@martinezguillaume it doesn’t quite work the same without the behavior prop. It only looks right if KeyboardAvoidingView is a full-screen view. If the KeyboardAvoidingView is nested in a view with a tabbar fixed to the bottom, for instance, the intended behavior is for the keyboard to hide the tab bar and push up the KeyboardAvoidingView. This works on iOS (with behavior=‘padding’ ) but does not work if you remove the behavior prop (on either platform)

Even stranger fix.

If I don’t set a container view for Android it works but breaks on iOS. Opposite is true if I do use a container view.

For example:

<!-- Works for Android but not iOS -->
<KeyboardAvoidingView>
    <View />
    <View />
</KeyboardAvoidingView>

<!-- Works for iOS but not Android -->
<KeyboardAvoidingView>
    <View>
        <View />
        <View />
    </View>
</KeyboardAvoidingView>

I got the same error and this saved me:

import { Platform } from 'react-native';

<KeyboardAvoidingView 	
	style={styles.yourStyle}
	behavior='padding'
	keyboardVerticalOffset={
		Platform.select({
			ios: () => -100,
			android: () => -120
		})()
	}
>
	...
</KeyboardAvoidingView> 

Change those values to fit your necessities on your platform, whether android or iOS

Here’s what we’re doing for our cross-platform solution. it’s been working fine for a long while:

In place of KeyboardAvoidingView, we use our own component that looks like

export class MySpecialKeyboardAvoidingView extends React.Component {
  render() {
    const Component = Platform.select({
      ios: KeyboardAvoidingView,
      android: View,
    });
    const {children, ...otherprops} = this.props;
    return (
      <Component behavior="padding" {...otherprops}>
        {children}
      </Component>
    );
  }
}

Then make sure In the AndroidManifest.xml you have

        <activity
            android:name=".react.MainReactActivity"
            android:windowSoftInputMode="adjustResize">
        </activity>

So in other words we’re using the react-native KeyboardAvoidingView for iOS but a plain old view on Android. As others have mentioned, the components who use the KeyboardAvoidingView should be the ones inside the Tab Navigator. This way the padding will be applied to the child page and the keyboard will properly cover the tabbar.

In my case, I was wrapping a login form in a KeyboardAvoidingView. Everything is working fine on IOS but the padding is too much on Android. I solved this by providing a wrapper component like this:

const Wrapper = props =>
  Platform.OS === 'ios' ?
  (
    <KeyboardAvoidingView behavior="padding">
      {props.children}
    </KeyboardAvoidingView>
  ) : (
    <View>
      {props.children}
    </View>
  );

And then wrapped my form content in this wrapper:

<Wrapper>
  {...my content}
</Wrapper>

And the padding looks perfect on both devices. Not sure if this works in other cases though.

Android is working out of the box for us, no keyboardAvoidingView used or change to manifest.xml

Solution we’re using is - Component file having a separate .ios.js version with

<KeyboardAvoidingView
  behavior='padding'
  style={{flex: 1}}
>

And just to be safe - make the change in Android’s manifest.xml, adding android:windowSoftInputMode="adjustPan"

Going to test this thoroughly but initially seems good

I had the same problem. The bug was solved after removing keyboardDidShow and keyboardDidHide listeners.

this.keyboardDidShowListener = DeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
this.keyboardDidHideListener = DeviceEventEmitter.addListener('keyboardDidHide', this.keyboardDidHide.bind(this));

If adjustNothing would fire the events, that would be great, since we don’t have to keep a ternary in the KeyboardAvoingView behavior and keep the same keyboard behavior in the whole app

Seems RN has a few ways to solve this, such as changing the KeyboardAvoidingView behavior to specify a negative bottom style in addition to height/padding, or making KeyboardAvoidingView work with the “adjustNothing” mode (currently it depends on Keyboard events that simply aren’t fired with “adjustNothing”).

Until it’s fixed, the only stable solution appears to be using “adjustResize”, which of course causes other problems, e.g. when using a bottom navbar (the navbar will be resized along with everything else).

So… any updates about this?

android:windowSoftInputMode="stateAlwaysVisible"

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

it’s works for me.

This is working for me on both Platforms:

<KeyboardAvoidingView
style={{ flexGrow: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={Platform.OS === 'ios' ? 40 : 0}>
...
</KeyboardAvoidingView>

Same here, when i added justifyContent:‘flex-end’ it worked

For me the reason where my styles.

If I gave the <KeyboardAvoidingView /> the following styles:

container: {
    flex: 1,
    justifyContent: "flex-start",
    backgroundColor: colors.$primaryWhite
  }

It didn’t work. But when I changed justifyContent to center it works on Android!

Same problem here, padding doubles height of the keyboard

In my case KeyboardAvoidingView with behavior=‘padding’ didn’t work. It look likes react-native uses twice the height of the keyboard. https://preview.ibb.co/n3xS3G/Schermafbeelding_2017_11_13_om_14_10_34.png

My app has a StackNavigator inside a TabNavigator import { TabNavigator, StackNavigator } from 'react-navigation' wrapped by Provider from react-redux as main entry point. What worked for me was to wrap whatever the components (that are being routed by the StackNavigator or TabNavigator, the ones with the input text inside) were rendering, with only one KeyboardAvoidingView with behavior='padding and also make it the main container for that component. For example:

   return(
      <KeyboardAvoidingView behavior='padding' style={container}>
        <View>
          <View >
            <Text >whatever</Text>
          </View>
          <View>
              <TextInput
                onChangeText={(text) => this.setState({text})}
                value={question}
              />
          </View>
          {
            Somebool &&
            <TouchableOpacity
              onPress={ handleSubmit }
            >
              <KeyboardAvoidingView behavior='padding'>
                <Text style={button} >Submit Info</Text>
              </KeyboardAvoidingView>
            </TouchableOpacity>
          }
        </View>
      </KeyboardAvoidingView>
   )

Not sure why but my Android manifest.xml solution stopped working a few weeks ago, and nothing I try seems to bring back the adjustPan behavior I want.

How great and React-like would it be if Keyboard just exposed some kind of dead-simple State boolean that we could rely on in layouts.

@clovs This actually worked ! , however to run successfully on android I needed to change behavior to “padding” in both platforms behavior= {"padding"}

I encounter the exam same problem. Have you found a fix? I use React Native 0.40.0