Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug in KeyboardAvoidingView on Android #11681

Closed
lukejagodzinski opened this issue Dec 30, 2016 · 76 comments

Comments

@lukejagodzinski
Copy link

commented Dec 30, 2016

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
@jeromegrosse

This comment has been minimized.

Copy link

commented Jan 25, 2017

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

@djw27

This comment has been minimized.

Copy link

commented Jan 31, 2017

We're also seeing this and have for a few RN versions now

@hungbya

This comment has been minimized.

Copy link

commented Feb 4, 2017

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));
@ippa

This comment has been minimized.

Copy link

commented Feb 6, 2017

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 :).

@fcFn

This comment has been minimized.

Copy link
Contributor

commented Feb 13, 2017

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.

@djw27

This comment has been minimized.

Copy link

commented Feb 14, 2017

This is fixed/explained in the upcoming release (v0.42) of RN e3d4ace

@lukejagodzinski

This comment has been minimized.

Copy link
Author

commented Feb 16, 2017

@djw27 that fix does not solve a problem :/

@christopherdro christopherdro added Android and removed Android labels Feb 20, 2017

@pgonzalez-santiago

This comment has been minimized.

Copy link

commented Mar 3, 2017

@jagi I'm having the same problem, Did you fix it ?

@lukejagodzinski

This comment has been minimized.

Copy link
Author

commented Mar 3, 2017

@pgonzalez-santiago no I didn't fix it so far. I'm just not using this view for Android for now, so there is at list no extra padding added. But it still hides input field when keyboard opens, and until I write something it's not visible. I hope it will be fixed in the future versions of RN

@pgonzalez-santiago

This comment has been minimized.

Copy link

commented Mar 3, 2017

@jagi, Doh! thanks.

@dcz-switcher

This comment has been minimized.

Copy link

commented Mar 12, 2017

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
@MattyK14

This comment has been minimized.

Copy link

commented Apr 24, 2017

I solved this problem a few months ago simply wrapping my TextInput with

<KeyboardAvoidingView behavior="padding" style={{ paddingTop: 50 }}>

Unsure when it stopped working properly but absolutely nothing i do to it makes a difference on Android unfortunately.

Running RN 0.43.1.

@andreleon

This comment has been minimized.

Copy link

commented Apr 25, 2017

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. e3d4ace

@MattyK14

This comment has been minimized.

Copy link

commented Apr 25, 2017

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

@RobinPapke

This comment has been minimized.

Copy link

commented May 1, 2017

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

@ollydixon

This comment has been minimized.

Copy link

commented May 31, 2017

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>
@martinezguillaume

This comment has been minimized.

Copy link
Contributor

commented Jun 19, 2017

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

@eflath

This comment has been minimized.

Copy link

commented Jun 21, 2017

@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)

@jtewright

This comment has been minimized.

Copy link

commented Jun 28, 2017

@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.

@martinezguillaume

This comment has been minimized.

Copy link
Contributor

commented Jul 3, 2017

Any update here ?

@kyle-ssg

This comment has been minimized.

Copy link

commented Jul 6, 2017

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

@Rovack

This comment has been minimized.

Copy link

commented Jul 31, 2017

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?

@jtewright

This comment has been minimized.

Copy link

commented Aug 3, 2017

Re. Android + navbar -- the solution I've got working is to have the navbar component listen to the keyboard and simply hide itself when the keyboard is up. Works very nicely.

I was experiencing very inconsistent behaviour and found that the way I'd set up my splash screen in AndroidManifest.xml was the cause: https://medium.com/@jtewright/setting-the-configchanges-and-windowsoftinputmode-on-the-splashactivity-caused-problems-with-71b937ee425b

Otherwise I've found I more or less need a bit of a custom set up for each scenario.

@Phenek

This comment has been minimized.

Copy link

commented Aug 4, 2017

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>
@kareem-adel

This comment has been minimized.

Copy link

commented Aug 16, 2017

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

@stale stale bot removed the Stale label Feb 24, 2018

@RWOverdijk

This comment has been minimized.

Copy link

commented Feb 24, 2018

I'm on 0.53.0... I hate bots.

@animaonline

This comment has been minimized.

Copy link

commented Mar 2, 2018

Needs to be reopened as this issue is present in 0.53.0

@wjielim

This comment has been minimized.

Copy link

commented Mar 8, 2018

We need this... really! This plays an important role in mobile app development.
It's still a problem in the latest version. Since it's working from the react-native team side, could the team show the working example? At least it will be clear which structure works and which didn't.

@kyle-ssg

This comment has been minimized.

Copy link

commented Mar 8, 2018

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

@rcorrie

This comment has been minimized.

Copy link

commented Mar 13, 2018

This ended up being my problem: #12980

Apparently, StatusBar doesn't play nicely with KeyboardAvoidingView on android.

If at any point you app hides the status bar, all bets are off with your KeyboardAvoidingView

@M1chaelChen

This comment has been minimized.

Copy link

commented Mar 14, 2018

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.

@maluramichael

This comment has been minimized.

Copy link

commented Mar 16, 2018

Iam using rn0.38 and got this problem too. But for me the statusbar is getting pushed out of the screen. The doesnt get resized. No matter which combination of windowSoftInputMode and behavior i use. Even if i remove the KeyboardAvoidingView completely.

@SSTPIERRE2

This comment has been minimized.

Copy link

commented Apr 3, 2018

Implementing for cross-platform keyboard avoiding scenarios has been an absolute nightmare for me. How can we move a good solution along here?

@eflath

This comment has been minimized.

Copy link

commented Apr 3, 2018

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.

@andrewdazs

This comment has been minimized.

Copy link

commented May 22, 2018

For me works by passing null to android:

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

@mbret

This comment has been minimized.

Copy link

commented Jul 10, 2018

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

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

@VinceBT

This comment has been minimized.

Copy link

commented Jul 10, 2018

@mbret indeed since 0.54 85bd98e

@matteocollina

This comment has been minimized.

Copy link

commented Sep 3, 2018

My solution

<KeyboardAvoidingView  
            behavior="padding" keyboardVerticalOffset={Platform.OS=="android" ? -500 : 84} enabled>
            <ScrollView alwaysBounceVertical={false}>
            <View >

....

@vdlindenmark

This comment has been minimized.

Copy link

commented Sep 3, 2018

@react-native-bot please reopen, still not working on newest release...

@janhesters

This comment has been minimized.

Copy link

commented Sep 7, 2018

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!

@faizalshap

This comment has been minimized.

Copy link

commented Oct 31, 2018

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

@MilosJo

This comment has been minimized.

Copy link

commented Nov 29, 2018

This problem still persists... I've tried all of the solutions but it seems I cant make Android to work...
Digging further, wish me luck since it is very annoying.

@Allan-Nava

This comment has been minimized.

Copy link

commented Dec 27, 2018

I got the same error!

@abranhe

This comment has been minimized.

Copy link

commented Dec 29, 2018

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

@boraerbasoglu

This comment has been minimized.

Copy link

commented Jan 1, 2019

Same here only justifyContent : 'center' and 'flex-end' works probably. 'space-between' is trying to pull up but not working

@boraerbasoglu

This comment has been minimized.

Copy link

commented Jan 1, 2019

By the way justifyContent : 'space-evenly' solved my issue

@onitzschke

This comment has been minimized.

Copy link

commented Jan 12, 2019

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>
@RichardLindhout

This comment has been minimized.

Copy link

commented Jan 19, 2019

This works with a smooth animation on iOS and works in Android.
I made a HOC component which you can wrap around every component where you want to have the keyboard height.

import React, { Component } from 'react'
import { Keyboard, Platform, LayoutAnimation } from 'react-native'

export default WrappedComponent => {
  class HOC extends Component {
    constructor(props) {
      super(props)

      this.state = {
        keyboardHeight: 0,
      }
    }

    componentWillMount() {
      if (WrappedComponent.componentDidMount)
        WrappedComponent.componentDidMount()

      if (Platform.OS === 'ios') {
        this.keyboardDidChangeListener = Keyboard.addListener(
          'keyboardWillChangeFrame',
          this._onKeyboardChange
        )
      } else {
        this.keyboardDidShowListener = Keyboard.addListener(
          'keyboardDidShow',
          this._keyboardDidShow
        )
      }

      this.keyboardDidHideListener = Keyboard.addListener(
        'keyboardDidHide',
        this._keyboardDidHide
      )
    }
    componentWillUnmount() {
      if (WrappedComponent.componentWillUnmount)
        WrappedComponent.componentWillUnmount()

      if (Platform.OS === 'ios') {
        this.keyboardDidChangeListener.remove()
      } else {
        this.keyboardDidShowListener.remove()
      }
      this.keyboardDidHideListener.remove()
    }

    _onKeyboardChange = event => {
      if (!event) {
        this.setState({ keyboardHeight: 0 })
        return
      }

      const { duration, easing, endCoordinates } = event
      const keyboardHeight = endCoordinates.height

      if (duration && easing) {
        LayoutAnimation.configureNext({
          duration,
          update: {
            duration,
            type: LayoutAnimation.Types[easing] || 'keyboard',
          },
        })
      }
      this.setState({ keyboardHeight })
    }
    _keyboardDidShow = e => {
      const { height } = e.endCoordinates

      if (height) {
        this.setState({
          keyboardHeight: height,
        })
      }
    }
    _keyboardDidHide = () => {
      this.setState({
        keyboardHeight: 0,
      })
    }

    render() {
      return <WrappedComponent {...this.state} {...this.props} />
    }
  }

  return HOC
}

For example: here is a KeyboardAvoidingScrollView

import React, { Component } from 'react'
import PT from 'prop-types'
import { View, ScrollView, StyleSheet } from 'react-native'
import keyboardHOC from './keyboardHOC'

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

class KeyboardAvoidingScrollView extends Component {
  render() {
    const { keyboardHeight } = this.props

    return (
      <View style={styles.flex}>
        <ScrollView {...this.props} />
        <View style={{ height: keyboardHeight }} />
      </View>
    )
  }
}

KeyboardAvoidingScrollView.propTypes = {
  children: PT.oneOfType([PT.arrayOf(PT.node), PT.node]).isRequired,
  keyboardHeight: PT.number.isRequired,
}
KeyboardAvoidingScrollView.defaultProps = {
  scrollToEnd: false,
}
export default keyboardHOC(KeyboardAvoidingScrollView)

@sinxwal

This comment has been minimized.

Copy link

commented Feb 25, 2019

I think this is the best workaround:
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined}>

@mutablestudio

This comment has been minimized.

Copy link

commented Mar 7, 2019

I tried a bit of everything here and this is currently working for me on iOS and Android API 28, Pixel 3 as well as API 27, Nexus 6P.

The keyboardVerticalOffset calculation is just a height percentage, so hp(20) is 20 percent of the window height (-120 for example).

<KeyboardAvoidingView style={styles.avoidingcontainer} behavior={Platform.OS === 'ios' ? 'padding' : 'position'} keyboardVerticalOffset={-(hp(20))}> ... </KeyboardAvoidingView>

avoidingcontainer: { flex: 1, justifyContent: "flex-start", },

@johnshadows

This comment has been minimized.

Copy link

commented Apr 12, 2019

android:windowSoftInputMode="stateAlwaysVisible"

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

it's works for me.

@JoeyBodnar

This comment has been minimized.

Copy link

commented Jul 11, 2019

android:windowSoftInputMode="stateAlwaysVisible"

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

it's works for me.

this does work, but android:windowSoftInputMode="stateAlwaysVisible" means that the keyboard will be up every time your app initially launches. I was able to get it working instead by using your answer + adjustUnspecified instead of stateAlwaysVisible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.