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.62k stars 67 forks source link

Inconsistent `NavigationBar` Behavior with `Edge-to-Edge` Support (Android) #592

Open wise-danya opened 1 week ago

wise-danya commented 1 week ago

Description

On Android, when toggling the enabled prop of the <KeyboardProvider /> using useKeyboardController, the system NavigationBar behaves inconsistently due to the handling of edge-to-edge mode:

When enabled is set to true, the NavigationBar disappears. When enabled is set to false, the NavigationBar reappears and causes layout issues by lifting the content. This issue is primarily caused by toggling edge-to-edge mode in the enable() and disable() methods.

Steps to reproduce the behavior

  1. Integrate react-native-keyboard-controller and react-native-bars into a project.
  2. Use the useKeyboardController hook to toggle the enabled prop of <KeyboardProvider />.
  3. Toggle enabled to true, observe that the system NavigationBar disappears.
  4. Toggle enabled to false, observe that the NavigationBar reappears and lifts the content.

Expected behavior

The NavigationBar should remain hidden or translucent and should not affect the layout or content positioning, regardless of the enabled state of the KeyboardProvider.

Repo for reproducing

You can find a minimal reproducible example for this issue in the following repository: https://github.com/wise-danya/rn-keyboard-controller-issue-example

Video Demo

https://github.com/user-attachments/assets/b24e775e-021a-4131-ba13-bd29a497cf73

Additional context

On some screens, when the library is disabled, the NavigationBar is visible and causes the content to be lifted, leading to layout issues. This makes it hard to gradually adopt the library across multiple screens in an app.

Environment:

kirillzyusko commented 6 days ago

Hey @wise-danya 👋 Thank you for providing a repo and high quality issue description!

Before digging into internals may I ask you what is the purpose of disabling the module and keeping edge-to-edge mode?

Basically the feature with disabling module was added to fallback to default Android adjustResize behavior. But if you keep edge-to-edge and just remove keyboard callbacks from native side, then you still will have edge-to-edge mode (i. e. automatic window resize will be disabled). So I'm kind of curious what is it the purpose you are following when trying to keep edge-to-edge and remove keyboard callbacks?

wise-danya commented 6 days ago

Hey @kirillzyusko 👋 Thanks for the quick response and for digging into the issue! Let me provide some additional context to clarify.

Purpose of Keeping Edge-to-Edge Mode

The reason I want to keep edge-to-edge mode enabled while using react-native-keyboard-controller is due to the usage of another library, react-native-bars. This library solves several issues with system bars and requires EdgeToEdge theme configuration in the styles.xml file:

<resources>
  <style name="AppTheme" parent="Theme.EdgeToEdge">
    <!-- … -->
  </style>
</resources>

Since the app’s theme relies on EdgeToEdge, the NavigationBar is expected to stay hidden or translucent. When react-native-keyboard-controller disables edge-to-edge mode, it conflicts with this setup and causes the NavigationBar to reappear, disrupting the layout.

Purpose of Disabling the Module

As for disabling the module, the main reason is to allow a gradual adaptation of react-native-keyboard-controller in the project. Not all screens are ready to use the keyboard controller functionality, and enabling the module across the app requires careful adjustments for various keyboard usage scenarios.

I’m following the approach you suggested for gradual adoption:

"If you want to gradually adopt the functionality of this package in your app, you can use useKeyboardController hook. You can disable the module by default <KeyboardProvider enabled={false}> and enable it using useKeyboardController on screens where you want to use it."

This allows me to selectively enable the module on screens where it has been fully integrated, while the rest of the app uses the default Android adjustResize behavior. However, the conflict between react-native-bars and react-native-keyboard-controller prevents me from providing a consistent visual experience across all screens.

Any workaround or ideas on how to avoid this conflict between the two libraries would be really helpful!

kirillzyusko commented 5 days ago

Thanks @wise-danya

I think now I understand better what you are trying to achieve.

Let me dig a little bit in internals and ask you about how you are going to handle them in your app 👀

1️⃣ edge-to-edge + adjustResize

As you pointed out earlier when you add react-native-bars it enters edge-to-edge mode. But when you refer to adjustResize behavior - it's kind of not truth adjustResize 😓

By default in Android when you activate edge-to-edge and adjustResize then you window will stop resizing 😱 react-native-bars handles that by implementing an additional logic and simulating old adjustResize behavior:

https://github.com/zoontek/react-native-bars/blob/2a7396f58282e30475a794e303a9fe93a920250d/android/src/main/java/com/zoontek/rnbars/RNBarsModuleImpl.java#L44-L63

So when you call RNBars.init(this); the Android OS doesn't resize the window, but react-native-bar will do that for you.

2️⃣ react-native-keyboard-controller integration

By default, when you wrap app with KeyboardProvider it shouldn't change the behavior (since you already in edge-to-edge mode). But! react-native-keyboard-controller and react-native-bars both setup onApplyWindowInsetsListener to rootView/decorView which may conflict with each other (i. e. when you disable/enable react-native-keyboard-controller).

3️⃣ react-native-bars keyboard handling

Honestly I didn't have a chance to clone your project and run reproduction. But I think there will be a conflict anyway, let's consider following cases:

So taking all these restrictions I don't think it's really possible to use setEnabled method if you use react-native-bars 😔

The potential solution would be to disable react-native-bars keyboard handling (by RNBars.init(this, false)). It will not resize the window anymore. And then you can wrap your entire app with KeyboardAvoidingView - theoretically it will give you the same behavior as you had behavior (just with one small difference - keyboard transitions will be animated now). And then you can write your own solution for disabling global KeyboardAvoidingView and usage of specific keyboard handling on specific screens.

Let me know what do you think and whether we are on the same page or not 😅 I'll clone your project to play around it later today 👀

RayKay91 commented 5 days ago

Hi, I think the issue was introduced from react-native-screens version 3.32.0. There is a fix that has been merged but not released yet that you can cherry pick to make it work as before, or patch it yourself with patch-package. I was getting a strange inset above and below that this PR fixes:

https://github.com/software-mansion/react-native-screens/pull/2301

kirillzyusko commented 5 days ago

@RayKay91 the provided example doesn't use react-native-screens directly or native-stack navigator in any form, so I highly doubt that it can be fixed by changes from react-native-screens (just because react-native-screens is not used in reproduction example).