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.58k stars 64 forks source link

iOS: Crash on @objc func windowDidBecomeHidden(_: Notification) { removeKVObserver() } #152

Closed gtokman closed 1 year ago

gtokman commented 1 year ago

Describe the bug

We have a crash that occurs on iOS when reading the reanimated keyboard height.

Code snippet

  const { height: keyboardHeight } = useReanimatedKeyboardAnimation()

Repo for reproducing It's in our main application. I can't provided a project to reproduce at this time.

Expected behavior A clear and concise description of what you expected to happen.

Screenshots

CleanShot 2023-05-15 at 14 15 03@2x
mobile[62515:8433225] *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <KeyboardMovementObserver 0x600007c9cba0> for the key path "center" from <UIInputSetHostView 0x1342e19e0> because it is not registered as an observer.'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000180437330 __exceptionPreprocess + 172
    1   libobjc.A.dylib                     0x0000000180051274 objc_exception_throw + 56
    2   Foundation                          0x0000000180b2f1e0 -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:] + 616
    3   Foundation                          0x0000000180b2f5bc -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:] + 132
    4   Foundation                          0x0000000180b2f4d0 -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:context:] + 184
    5   mobile                              0x00000001033eaf6c $s32react_native_keyboard_controller24KeyboardMovementObserverC16removeKVObserver33_98D42DC26656EA4CABF65DBC7E7C9563LLyyF + 192
    6   mobile                              0x00000001033eabb4 $s32react_native_keyboard_controller24KeyboardMovementObserverC21windowDidBecomeHiddenyy10Foundation12NotificationVF + 36
    7   mobile                              0x00000001033eac38 $s32react_native_keyboard_controller24KeyboardMovementObserverC21windowDidBecomeHiddenyy10Foundation12NotificationVFTo + 120
    8   CoreFoundation                      0x000000018036bff8 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 140
    9   CoreFoundation                      0x000000018036bf1c ___CFXRegistrationPost_block_invoke + 84
    10  CoreFoundation                      0x000000018036b424 _CFXRegistrationPost + 404
    11  CoreFoundation                      0x000000018036ae10 _CFXNotificationPost + 664
    12  Foundation                          0x0000000180b5309c -[NSNotificationCenter postNotificationName:object:userInfo:] + 88
    13  UIKitCore                           0x0000000116dd3314 -[UIWindow _setHidden:forced:] + 680
    14  UIKitCore                           0x0000000116c380ec -[_UIRemoteKeyboards setWindowEnabled:force:] + 324
    15  UIKitCore                           0x0000000116c3a6b4 -[_UIRemoteKeyboards setWindowLevel:sceneLevel:forResponder:] + 588
    16  UIKitCore                           0x000000011683f8f8 -[UIKeyboardSceneDelegate setWindowLevel:sceneLevel:forResponder:] + 144
    17  UIKitCore                           0x000000011684048c -[UIKeyboardSceneDelegate setTextEffectsWindowLevelForInputView:responder:] + 864
    18  UIKitCore                           0x0000000116843cc4 -[UIKeyboardSceneDelegate setKeyWindowSceneInputViews:animationStyle:] + 524
    19  UIKitCore                           0x0000000116843a84 -[UIKeyboardSceneDelegate setInputViews:animationStyle:] + 132
    20  UIKitCore                           0x0000000116844a54 -[UIKeyboardSceneDelegate setInputViews:animated:] + 72
    21  UIKitCore                           0x0000000116844aa4 -[UIKeyboardSceneDelegate setInputViews:] + 52
    22  UIKitCore                           0x0000000116842b48 __102-[UIKeyboardSceneDelegate _reloadInputViewsForKeyWindowSceneResponder:force:fromBecomeFirstResponder:]_block_invoke.314 + 24
    23  UIKitCore                           0x0000000116c10d0c __65-[UIPeripheralHost(UIKitInternal) queueDelayedTask:forKey:delay:]_block_invoke + 156
    24  libdispatch.dylib                   0x0000000106bb1d50 _dispatch_client_callout + 16
    25  libdispatch.dylib                   0x0000000106bb5208 _dispatch_continuation_pop + 756
    26  libdispatch.dylib                   0x0000000106bcc8d4 _dispatch_source_invoke + 1676
    27  libdispatch.dylib                   0x0000000106bc2634 _dispatch_main_queue_drain + 848
    28  libdispatch.dylib                   0x0000000106bc22d4 _dispatch_main_queue_callback_4CF + 40
    29  CoreFoundation                      0x000000018039a784 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
    30  CoreFoundation                      0x0000000180394de4 __CFRunLoopRun + 1912
    31  CoreFoundation                      0x0000000180394254 CFRunLoopRunSpecific + 584
    32  GraphicsServices                    0x0000000188eb7c9c GSEventRunModal + 160
    33  UIKitCore                           0x0000000116d9aff0 -[UIApplication _run] + 868
    34  UIKitCore                           0x0000000116d9ef3c UIApplicationMain + 124
    35  mobile                              0x00000001027f7ed8 main + 96
    36  dyld                                0x0000000106579514 start_sim + 20
    37  ???                                 0x0000000106699f28 0x0 + 4402552616
    38  ???                                 0x3a17800000000000 0x0 + 4185955116152520704
)

Smartphone (please complete the following information):

kirillzyusko commented 1 year ago

@gtokman I guess it happens on latest version of the library, right?

As an intermediate solution I can suggest to revert back to 1.4.4

In a meantime I'll try to find a solution and provide a fix 🙂

gtokman commented 1 year ago

@kirillzyusko yes, on the latest version of the library and 2.14 for reanimated.

kirillzyusko commented 1 year ago

@gtokman could you please also tell more about how do you use the library and when this exception occurs?

How many KeyboardProviders do you have? When this exception occurs (I guess when keyboard becomes hidden, but how long do you need to show/hide keyboard before you meet an exception? Maybe you see the exception when you switch between different type of keyboards (like number/alphabetical keyboard)?

kirillzyusko commented 1 year ago

@gtokman also, if you are able to test, could you please try to change:

keyboardView?.removeObserver(self, forKeyPath: "center", context: nil)

to

_keyboardView?.removeObserver(self, forKeyPath: "center", context: nil)

Just add _ to keyboardView variable.

I have an assumption, that when we refer to keyboardView it may re-run a process of finding a view and return a new instance without a listener 🤷‍♂️

kirillzyusko commented 1 year ago

@gtokman could you please check whether https://github.com/kirillzyusko/react-native-keyboard-controller/pull/157 fixes the problem?

cgav commented 1 year ago

@gtokman could you please check whether #157 fixes the problem?

@kirillzyusko I had the same issue and applying the patch from your PR fixes the crash for me. Thank you 👍

kirillzyusko commented 1 year ago

Awesome @cgav Glad to know the fix is actually working! I'm going to merge this PR within this week and prepare a new release, since it's quite impactful change 😎

gunnartorfis commented 1 year ago

FYI I'm also having this issue. I'm just using the KeyboardProvider in my App.tsx, and then in one place in my app:

 React.useEffect(() => {
    const show = KeyboardEvents.addListener('keyboardWillShow', () => {
      bottomSheetRef.current?.expand();
    });

    return () => {
      show.remove();
    };
}, []);

The thing is that I can't reproduce this myself but Sentry has reported over 2.5k users experiencing this. What's weird to me about that 2.5k number is that the feature where the above code is, isn't much used. Could it be that the crash occurs when just applying the KeyboardProvider?

kirillzyusko commented 1 year ago

Hi @gunnartorfis

Yes, the issue will occur when you've wrapped app into KeyboardProvider (underhood it'll render KeyboardControllerView which setup all listeners and etc.).

@gunnartorfis could you please try to apply patch from https://github.com/kirillzyusko/react-native-keyboard-controller/pull/157 and see whether the amount of errors will be decreased?

gunnartorfis commented 1 year ago

Hi @gunnartorfis

Yes, the issue will occur when you've wrapped app into KeyboardProvider (underhood it'll render KeyboardControllerView which setup all listeners and etc.).

@gunnartorfis could you please try to apply patch from #157 and see whether the amount of errors will be decreased?

I see, thanks. Since I'm unable to reproduce the issue myself I don't want to release the patch to production until it has been released. If you have any ideas on how I might reproduce this myself then I'd be happy to. It seems to be hitting quite the range of iOS versions, etc - I'm on 16.3 myself.

Screenshot 2023-06-01 at 09 12 22 Screenshot 2023-06-01 at 09 12 58 Screenshot 2023-06-01 at 09 12 18
kirillzyusko commented 1 year ago

@gunnartorfis that's the problem that I don't know how to reproduce this crash 🤷‍♂️ I can not reproduce the problem in example app and I don't have reproducible example.

I have only guesses, that the reference to the keyboard view is changing and I'm trying to remove an observer which doesn't exist anymore... I really hope this patch can fix everything - if not, then I'll be happy to continue investigation on how to resolve the problem. The new version of the library 1.5.4 will be published today.

@gunnartorfis since you are not using other functionality apart of keyboardWillShow you can safely downgrade keyboard-controller version to 1.4.4 - this version will not have crashes, since I'm not using KVO mechanism in this version.

gunnartorfis commented 1 year ago

@kirillzyusko Thanks for the info. I will downgrade for now and keep a close eye on this.

Feel free to tag me if there's anything I can do to help out.

kirillzyusko commented 1 year ago

GitHub automatically closed this issue since PR that had a reference to this issue was merged.

I've published 1.5.4 to npm-registry. Hopefully the issue is resolved, but in any case feel free to re-open this issue or feel free to create a new one.

bcgilliom commented 6 months ago

I'm seeing this issue in bugsnag with 1.10.3 Here's the stack trace:

Hardware Model:     iPhone14,5
Process:            App
Identifier:         com.app
Role:               Foreground
OS Version:         iOS 16.5

NSRangeException: Cannot remove an observer <KeyboardMovementObserver 0x283160310> for the key path "center" from <UIInputSetHostView 0x10b613c70> because it is not registered as an observer.

0  CoreFoundation +0x9cb0      ___exceptionPreprocess
1  libobjc.A.dylib +0x183cc    _objc_exception_throw
2  Foundation +0x6621c         -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:]
3  Foundation +0x65f5c         -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:]
4  Foundation +0x65e6c         -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:context:]
5  App +0x571850           KeyboardMovementObserver.removeKVObserver() (KeyboardMovementObserver.swift:98:20)
6  App +0x572b50           @objc KeyboardMovementObserver.keyboardWillAppear(_:)
7  CoreFoundation +0x3758c     ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
8  CoreFoundation +0xdb824     ____CFXRegistrationPost_block_invoke
9  CoreFoundation +0xbe8b4     __CFXRegistrationPost
10 CoreFoundation +0x4baf8     __CFXNotificationPost
11 Foundation +0x5cd34         -[NSNotificationCenter postNotificationName:object:userInfo:]
12 UIKitCore +0xb7ed9c         -[UIInputWindowController postNotificationName:userInfo:]
13 UIKitCore +0x39245c         ___68-[UIInputWindowController postValidatedStartNotifications:withInfo:]_block_invoke
14 UIKitCore +0x3b2e28         -[UIInputWindowController postValidatedStartNotifications:withInfo:]
15 UIKitCore +0x314424         -[UIInputWindowController postStartNotifications:withInfo:]
16 UIKitCore +0xb7fad4         ___77-[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:]_block_invoke_4
17 UIKitCore +0x3d8ad0         +[UIView(UIViewAnimationWithBlocksPrivate) _modifyAnimationsWithPreferredFrameRateRange:updateReason:animations:]
18 UIKitCore +0xcd2ac          +[UIView _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:]
19 UIKitCore +0x5538c0         +[UIView(UIViewAnimationWithBlocks) _animateWithDuration:delay:options:animations:start:completion:]
20 UIKitCore +0x2ce2b4         -[UIInputViewAnimationStyle launchAnimation:afterStarted:completion:forHost:fromCurrentPosition:]
21 UIKitCore +0x2ce174         -[_UIViewControllerKeyboardAnimationStyle _launchAnimation:afterStarted:completion:forHost:fromCurrentPosition:]
22 UIKitCore +0x2cde80         +[UIView(Animation) _performWithAnimation:]
23 UIKitCore +0x2cddc8         -[_UIViewControllerKeyboardAnimationStyle launchAnimation:afterStarted:completion:forHost:fromCurrentPosition:]
24 UIKitCore +0xb807d0         ___77-[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:]_block_invoke.459
25 UIKitCore +0x36f4e4         -[UIInputWindowController chainPlacementsIfNecessaryFrom:toPlacement:transition:completion:]
26 UIKitCore +0x36ca9c         -[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:]
27 UIKitCore +0x36bc00         -[UIInputWindowController setInputViewSet:]
28 UIKitCore +0x36b638         -[UIInputWindowController performOperations:withAnimationStyle:]
29 UIKitCore +0x27b92c         -[UIKeyboardSceneDelegate setKeyWindowSceneInputViews:animationStyle:]
30 UIKitCore +0x27ae20         -[UIKeyboardSceneDelegate setInputViews:animationStyle:]
31 UIKitCore +0x204678         -[UIKeyboardSceneDelegate setInputViews:animated:]
32 UIKitCore +0x20168c         -[UIKeyboardSceneDelegate setInputViews:]
33 UIKitCore +0x8ea2e4         ___102-[UIKeyboardSceneDelegate _reloadInputViewsForKeyWindowSceneResponder:force:fromBecomeFirstResponder:]_block_invoke.329
34 UIKitCore +0x8e9d74         -[UIKeyboardSceneDelegate _reloadInputViewsForKeyWindowSceneResponder:force:fromBecomeFirstResponder:]
35 UIKitCore +0x8e8a30         -[UIKeyboardSceneDelegate _reloadInputViewsForResponder:force:fromBecomeFirstResponder:]
36 UIKitCore +0x27c8c0         -[UIResponder(UIResponderInputViewAdditions) reloadInputViews]
37 UIKitCore +0x21a44c         -[UIResponder becomeFirstResponder]
38 UIKitCore +0xfdb73c         ___99-[_UIRemoteViewController _serviceWantsKeyboardEventsWithPromotedFirstResponder:completionHandler:]_block_invoke
kirillzyusko commented 6 months ago

@bcgilliom can you please test 1.11.0 and if it's still reproducible, then open a new issue? 🙏

And maybe you can shed some light on additional aspects, such as how frequently it happens, how much users are affected (in percentage, like 0.02% or smth like that), and maybe when it happens (maybe user navigated from modal screen to non-modal or something else)?

bcgilliom commented 6 months ago

@kirillzyusko it seems to be extremely rare - we had a few reports early on, so I wasn't sure at first. I'll bump the library to latest and report back 👍 Thanks for all of your help and work on this lib. It's been great!