kirillzyusko / react-native-keyboard-controller

Keyboard manager which works in identical way on both iOS and Android
https://kirillzyusko.github.io/react-native-keyboard-controller/
MIT License
1.54k stars 61 forks source link

Opening Modal prevents proper resizing of layout #387

Closed krystianhub closed 1 month ago

krystianhub commented 5 months ago

Describe the bug When the keyboard is open, launching a Modal (from either the react-native or react-native-modal package) results in the keyboard closing but without correctly adjusting the layout size.

Code snippet

import { useState } from 'react';
import { Button,  FlatList,  StyleSheet, Text, TextInput, View } from 'react-native';
import { KeyboardAwareScrollView, KeyboardProvider } from 'react-native-keyboard-controller';
import Modal from 'react-native-modal';

const data = Array.from(Array(50).keys());

export default function App() {
  const [modalVisible, setModalVisible] = useState(false);

  const onPress = () => {
    // Partial workaround: uncomment me and set Modal coverScreen to false
    // KeyboardController.dismiss();
    setModalVisible(true);
  };

  return (
    <KeyboardProvider>

      <Modal isVisible={modalVisible} coverScreen={true}>
        <View style={{flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'black'}}>
          <Text style={{color: 'white'}}>Modal content here</Text>

          <Button title='Close modal' onPress={() => setModalVisible(false)} />
        </View>
      </Modal>

      <KeyboardAwareScrollView contentContainerStyle={styles.container} keyboardShouldPersistTaps={"always"}>

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

        <Button title="Open modal"  onPress={onPress}/>

        <FlatList scrollEnabled={false} data={data} style={{height: 500}} renderItem={({index}) => <Text style={{textAlign: 'center'}}>{index}</Text>}/>

        <TextInput placeholder='text input' style={{backgroundColor: 'white', padding: 10}} />

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

      </KeyboardAwareScrollView>
    </KeyboardProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 50,
  },
});

Repo for reproducing https://github.com/krystianhub/RN-Modal-Issue - Expo Managed workflow; requires compiled app with expo-dev-client. I can provide the apk if needed. Alternatively, to build it locally yourself:

eas build --profile development --platform android --local

To Reproduce Steps to reproduce the behavior:

  1. Tap on text input element
  2. Keyboard is open
  3. Tap on open modal
  4. Modal is visible, keyboard is closed
  5. Tap on close modal
  6. Layout is incorrectly shifted upwards

Expected behavior Keyboard should close and properly resize layout back to its default height. Video of expected behaviour (recorded with disabled Keyboard Provider, same as default RN behaviour): https://github.com/kirillzyusko/react-native-keyboard-controller/assets/1358334/afb4ac15-82a9-4e3e-86eb-a5524d50a4ba

Screenshots Video of the current behaviour: https://github.com/kirillzyusko/react-native-keyboard-controller/assets/1358334/ca572fdd-72f9-406d-88cd-cc3070a3a1e6

Smartphone (please complete the following information):

Additional context I discovered a partial solution by adjusting the "coverScreen" attribute to "false" within the Modal element and manually dismissing the keyboard when pressing the "Open Modal" button simultaneously. Nonetheless, when dealing with navigation and headers, the opacity of the Modal's background only extends to the parent view rather than the entire screen. Regrettably, this workaround doesn't fully meet my expectations.

Video of the workaround: https://github.com/kirillzyusko/react-native-keyboard-controller/assets/1358334/bd90fd3d-aa04-44e7-ad99-e308e5c9f448

krystianhub commented 5 months ago

I also attempted to use the KeyboardAvoidingView component instead, but encountered the same issue. It appears that when the Modal is displayed (with coverScreen=true), the animation breaks, leaving layout in the same position as when keyboard is open.

kirillzyusko commented 5 months ago

Hi @krystianhub

Thank you for opening the issue ❤️

Indeed this is kind of known issue. The problem is that as soon as modal appears it handles keyboard on its own way and prevents events propagation to the root view - as a result I'm not receiving events that keyboard is hidden and because of that you see a space which is equal to keyboard size (even if keyboard is already closed).

The fix should be implemented on a native side, but right now there is no way to get a window of modal in RN (see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/369 for more details).

So as a workaround you have 2 choices:

import {
  KeyboardController,
  KeyboardEvents,
} from "react-native-keyboard-controller";

let isClosed = true;

KeyboardEvents.addListener("keyboardDidHide", _ => {
  isClosed = true;
});
KeyboardEvents.addListener("keyboardDidShow", _ => {
  isClosed = false;
});

const utils = {
  waitToBeClosed: () =>
    new Promise(resolve => {
      if (isClosed) {
        resolve(undefined);
        return;
      }

      const subscription = KeyboardEvents.addListener("keyboardDidHide", _ => {
        resolve(undefined);
        subscription.remove();
      });
      KeyboardController.dismiss();
    }),
};

export default utils;

and the before showing a modal we use it like:

import KeyboardUtils from './keyboard';
// ...
const onPress = async () => {
  await KeyboardUtils.waitToBeClosed();
  setModalVisible(true);
};

This is not the best solution, but gives quite pleasant UI and UX so... as a temporary solution we use it 🙂

As I said before - I'm planning to fix the integration with Modal windows, but it'll take some time, because I'll need to change a native code in Modal component from react-native (and it'll take time to review/merge my changes).

krystianhub commented 5 months ago

Thank you for your quick response. I had been considering a similar solution to yours (an async helper that waits until the keyboard is fully hidden), but your implementation is better. After some initial testing, I'm pleased with the result!

Thanks!