Hello, I’ve been at a loss so far with this. Im writing also to see if someone can give me more hindsight in this problem or what is going on. I’ve been developing an app on Expo sdk36 for at least 4 months now and normally when introducing text on an Input the app didn’t try to resize the views so the keyboard doesn’t cover them.
I implemented an animated view to hanlde such cases in a long form I have. Following articles I’ve found it was working perfectly on both platforms. On IOS the animation was more smooth because the keyboard events returned a somewhat decent duration meanwhile on Android the duration defaults to 0 from the event itself, but at least it only resized the View where I had the scroll with the Form.
Yesterday, I’m not sure if it’s due to the new version of expo client and sdk or what but on Android it suddenly started resizing the whole view when inputing a text. When that happened I did a rollback of the proyect back to sdk 36 but the issue persisted, I attribute to the fact that on my phone I already updated my expo client to the last version. On the android simulator, where the expo client is not yet updated to handle sdk37 it still works as before. From what I’ve read that’s the normal behavior on Android but the problem I have, after all this context, is the following:
On IOS it works as expected, of course because IOS doesn’t try to resize the view whenever an input is shown but on Android something weird is happening, the animation fires but when the view is finally resized, the keyboard gets dismissed immediately, I tested this by giving Animated.timing() a much longer duration and it was indeed executing it but when it finished, the keyboard gets dismissed and as such the view goes back immediately, not allowing the user to input any text.
Right now I added a validation so it doesn’t execute the animation on Android to avoid the issue, but I hope someone can see my problem and be able to explain to me what I’m doing wrong or if this is the expected behaviour on Android, or if I’m using a wrong library or a wrong implementation, I’ve been looking everywhere but I haven’t found anything of the sort and the only solution that could work in my case would be ejecting from Expo and changing android:windowSoftInputMode=“adjustResize”, which I can’t because of the requirements of the proyect I’m currently in.
This is the implementation I’m using for the view with the registration form:
import React, { Component, Fragment } from 'react';
import { TouchableOpacity, Text, View, ImageBackground, Animated, Keyboard, Platform, ScrollView, ActivityIndicator } from 'react-native';
import styles from './styles';
import moment from 'moment';
import DateTimePickerModal from "react-native-modal-datetime-picker";
import FormInput from '../../../components/formInput';
import { Formik } from 'formik';
import { Icon } from 'react-native-elements';
import { firstFormValidation } from './formValidations';
import { moderateScale } from '../../../styles/globalStyles';
export class RegisterOne extends Component {
constructor(props) {
super(...arguments);
this.state = {
firstForm: {
nombre: '',
apellidos: '',
cedula: '',
fechaNacimiento: '',
NIT: '',
cargo: '',
},
show: false,
keyboardHeight: new Animated.Value(0)
}
};
componentDidMount() {
// Creamos Listeners para detectar cuando aparece o desaparece el teclado
// Listeners to subscribe to keyboard events according to the platform and execute the resizing animation
this.keyboardWillShowSub = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', this.keyboardWillHide);
};
componentWillUnmount() {
// Limpiamos los Listeners de los eventos del teclado
// Remove subscriptions on component unmount
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
};
keyboardWillShow = (event) => {
if (Platform.OS === 'ios') {
Animated.timing(this.state.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
}).start();
}
};
keyboardWillHide = (event) => {
Animated.timing(this.state.keyboardHeight, {
duration: event.duration,
toValue: 0,
}).start();
};
show = () => {
this.setState({
...this.state,
show: true,
});
};
closePicker = () => {
this.setState({
...this.state,
show: false
})
};
handleConfirm = (date,formikProps,name) => {
this.closePicker();
formikProps.setFieldValue(name, moment(date).format('YYYY-MM-DD'));
};
renderFormDateSelector(formikProps, name, placeholder, password = false) {
return (
<View style={styles.formInput}>
<FormInput
secureTextEntry={password}
placeholder={placeholder}
editable={false}
rightIcon={
<TouchableOpacity onPress={() => this.show()}>
<Icon size={moderateScale(14)} type='feather' name={this.state.show ? 'chevron-up' : 'chevron-down'} color='grey'/>
</TouchableOpacity>
}
>
{ formikProps.values[name] !== '' ? moment(formikProps.values[name]).format('YYYY-MM-DD') : ''}
</FormInput>
{this.state.show &&
<DateTimePickerModal
mode={'date'}
isVisible={this.state.show}
maximumDate={moment().toDate()}
onConfirm={(date) => this.handleConfirm(date,formikProps,name)}
onCancel={this.closePicker}
/>
}
</View>
);
};
renderFormInput(formikProps, name, placeholder, password = false) {
return (
<View style={styles.formInput}>
<FormInput
name={name}
secureTextEntry={password}
value={formikProps.values[name]}
placeholder={placeholder}
onBlur={formikProps.handleBlur(name)}
onChangeText={formikProps.handleChange(name)}
errorMessage={formikProps.touched[name] && formikProps.errors[name]}
/>
</View>
);
};
render() {
return (
<ImageBackground source={require("../../../assets/splashScreens/fondo_login.png")} style={{width: '100%', height: '100%'}}>
<View style={styles.container}>
<View style={styles.titleContainer}>
<Text style={styles.title}>SOLICITUD DE REGISTRO</Text>
</View>
<View style={styles.formContainer}>
<View style={styles.formTitleContainer}>
<Icon type='material-community' name='account-key' color='white' size={moderateScale(50)}/>
<Text style={styles.formTitle}>Paso 1 de 2. Información Personal</Text>
</View>
<View style={styles.formInputs}>
<Formik
initialValues={{
nombre: '',
apellidos: '',
cedula: '',
fechaNacimiento: '',
NIT: '',
cargo: ''
}}
onSubmit={async (values, actions) => {
// Guardamos los valores en el estado para poder enviarlos a la segunda parte
this.setState({
...this.state,
firstForm: {
...values
},
}, () => {
this.props.navigation.navigate('RegisterTwo', {firstForm: {...this.state.firstForm}})
});
}}
validationSchema={firstFormValidation}
>
{
formikProps => (
<Fragment>
<Animated.View style={{ flex: 0.8, margin: moderateScale(10), paddingBottom: this.state.keyboardHeight}}>
<ScrollView style={{height: 300}}>
{this.renderFormInput(formikProps,'nombre','Nombre')}
{this.renderFormInput(formikProps,'apellidos','Apellidos')}
{this.renderFormInput(formikProps,'cedula','Cédula o Doc. Identidad')}
{this.renderFormDateSelector(formikProps,'fechaNacimiento','Fecha de Nacimiento')}
{this.renderFormInput(formikProps,'NIT','NIT de la Empresa (Sin digito de verificación)')}
{this.renderFormInput(formikProps,'cargo','Cargo en la Empresa')}
</ScrollView>
</Animated.View>
<View style={styles.formButtonContainer}>
<TouchableOpacity disabled={formikProps.isSubmitting} style={styles.button} onPress={() => formikProps.handleSubmit()}>
{!formikProps.isSubmitting ? (
<Text style={styles.buttonText}>CONTINUAR</Text>
):(
<ActivityIndicator style={{flex: 1, alignSelf: 'center'}} size='large'/>
)
}
</TouchableOpacity>
</View>
</Fragment>
)
}
</Formik>
</View>
</View>
</View>
</ImageBackground>
)
}
};