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 · 77 comments
Closed

Bug in KeyboardAvoidingView on Android #11681

lukejagodzinski opened this issue Dec 30, 2016 · 77 comments
Labels
Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.

Comments

@lukejagodzinski
Copy link

lukejagodzinski 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
Copy link

jeromegrosse commented Jan 25, 2017

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

@djw27
Copy link

djw27 commented Jan 31, 2017

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

@phibya
Copy link

phibya 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
Copy link

ippa 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
Copy link
Contributor

fcFn 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
Copy link

djw27 commented Feb 14, 2017

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

@lukejagodzinski
Copy link
Author

@djw27 that fix does not solve a problem :/

@pgonzalez-santiago
Copy link

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

@lukejagodzinski
Copy link
Author

@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
Copy link

@jagi, Doh! thanks.

@dcz-switcher
Copy link

dcz-switcher 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
Copy link

MattyK14 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
Copy link

andreleon 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
Copy link

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

@RobinPapke
Copy link

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

@ollyde
Copy link

ollyde 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
Copy link
Contributor

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

@eflath
Copy link

eflath 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
Copy link

jtewright 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
Copy link
Contributor

Any update here ?

@kyle-ssg
Copy link

kyle-ssg 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
Copy link

Rovack 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
Copy link

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
Copy link

Phenek 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
Copy link

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

@RWOverdijk
Copy link

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

@animaonline
Copy link

Needs to be reopened as this issue is present in 0.53.0

@wjielim
Copy link

wjielim 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
Copy link

kyle-ssg commented Mar 8, 2018

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

@rcorrie
Copy link

rcorrie 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
Copy link

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
Copy link

maluramichael 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
Copy link

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

@eflath
Copy link

eflath 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
Copy link

For me works by passing null to android:

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

@mbret
Copy link

mbret 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
Copy link

VinceBT commented Jul 10, 2018

@mbret indeed since 0.54 85bd98e

@matteocollina
Copy link

My solution

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

....

@vdlindenmark
Copy link

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

@janhesters
Copy link

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
Copy link

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

@MilosJo
Copy link

MilosJo 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
Copy link

I got the same error!

@abranhe
Copy link

abranhe 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
Copy link

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

@boraerbasoglu
Copy link

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

@onitzschke
Copy link

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
Copy link

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
Copy link

sinxwal commented Feb 25, 2019

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

@mutablestudio
Copy link

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
Copy link

johnshadows 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
Copy link

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

@kdo1234
Copy link

kdo1234 commented Dec 1, 2019

This approach works sometimes for me. However, the keyboard frequently hides immediately after being shown. Does anyone have any advice on what to experiment with? Thank you.

android:windowSoftInputMode="adjustUnspecified"

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

@facebook facebook locked as resolved and limited conversation to collaborators Dec 11, 2019
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Dec 11, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests