expo: Expo Notifications crashes app on reload

Summary

Expo Notifications 0.14.0 will crash an expo-dev-client build on an Ios device on app reload.

  1. Open the app on the device.
  2. Hit “r” in the terminal.

The app will crash with…

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: onDevicePushToken with body: {
    devicePushToken = ***ACTUAL TOKEN REMOVED***;
}. Bridge is not set. This is probably because you've explicitly synthesized the bridge in EXReactNativeEventEmitter, even though it's inherited from RCTEventEmitter.'

The app does not crash when using Expo Go.

Somewhat similar issue. https://github.com/expo/expo/issues/3276

Managed or bare workflow? If you have ios/ or android/ directories in your project, the answer is bare!

managed

What platform(s) does this occur on?

iOS

SDK Version (managed workflow only)

44

Environment

Expo CLI 5.0.3 environment info: System: OS: macOS 12.1 Shell: 5.8 - /bin/zsh Binaries: Node: 16.13.0 - ~/.nvm/versions/node/v16.13.0/bin/node Yarn: 1.22.17 - ~/.nvm/versions/node/v16.13.0/bin/yarn npm: 8.3.0 - ~/.nvm/versions/node/v16.13.0/bin/npm Managers: CocoaPods: 1.11.2 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.2, iOS 15.2, macOS 12.1, tvOS 15.2, watchOS 8.3 IDEs: Xcode: 13.2.1/13C100 - /usr/bin/xcodebuild npmPackages: expo: ~44.0.0 => 44.0.3 react: 17.0.1 => 17.0.1 react-dom: 17.0.1 => 17.0.1 react-native: 0.64.3 => 0.64.3 react-native-web: 0.17.1 => 0.17.1 npmGlobalPackages: eas-cli: 0.43.0 expo-cli: 5.0.3 Expo Workflow: managed

Reproducible demo

import {View} from 'react-native';
import {useEffect} from 'react';
import * as Notifications from 'expo-notifications';

export default function App() {
    useEffect(() => {
        const subscription = Notifications.addNotificationResponseReceivedListener(response => {});
        return () => subscription.remove();
    }, []);
    return <View />
}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 30
  • Comments: 40 (12 by maintainers)

Commits related to this issue

Most upvoted comments

Just importing import * as Notifications from ‘expo-notifications’; is enough for my app to crash with this error.

Did some digging, found that this line leads to crash on dev client reloads. This line is calling registerForRemoteNotifications on the native side which emits event to JS side at some point. This assertion is crashing the app.

Attaching some logs when it crashes. It tries to send the event but the bridge is not set. I am not sure why the bridge could be nil, maybe some race condition, will have to dig more 😅

2022-03-17 14:49:34.230406+0530 Showtime[24248:5821450] [javascript] Running "main" with {"rootTag":31,"initialProps":{}}
2022-03-17 14:49:34.300126+0530 Showtime[24248:5820874] *** Assertion failure in -[RCTEventEmitter sendEventWithName:body:](), /Users/nishanbende/Desktop/nishan/work/showtime-frontend/node_modules/react-native/React/Modules/RCTEventEmitter.m:58
2022-03-17 14:49:34.300927+0530 Showtime[24248:5820874] DevLauncher tries to handle uncaught exception: Error when sending event: onDevicePushToken with body: {
    devicePushToken = 2f21636b42bd83caf8d4f79604849b79e0e36488bee3cf318c11dca46c09b0bb;
}. Bridge is not set. This is probably because you've explicitly synthesized the bridge in EXReactNativeEventEmitter, even though it's inherited from RCTEventEmitter.
2022-03-17 14:49:34.310305+0530 Showtime[24248:5820874] Stack Trace: (
    "0   CoreFoundation                      0x0000000180a29110 E2F84645-2905-31EF-8EC7-3CC19C3CDDB3 + 626960",
    "1   libobjc.A.dylib                     0x0000000199279d64 objc_exception_throw + 60",
    "2   Foundation                          0x00000001822e5504 925A43CD-EAF2-3161-9378-3ED87468301D + 1246468",
    "3   Showtime                            0x00000001049681d4 -[RCTEventEmitter sendEventWithName:body:] + 452",
    "4   Showtime                            0x000000010509e8d0 __copy_helper_block_e8_32s40b48b56w + 23560",
    "5   Showtime                            0x000000010509e184 __copy_helper_block_e8_32s40b48b56w + 21692",
    "6   Showtime                            0x0000000104187614 -[EXLegacyAppDelegateWrapper application:didRegisterForRemoteNotificationsWithDeviceToken:] + 324",
    "7   Showtime                            0x00000001041d27a8 $sTa.171 + 32",
    "8   Showtime                            0x00000001041cae40 $sSo13UIApplicationCSo6NSDataCIegyy_AB10Foundati
2022-03-17 14:49:34.310918+0530 Showtime[24248:5820874] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: onDevicePushToken with body: {
    devicePushToken = 2f21636b42bd83caf8d4f79604849b79e0e36488bee3cf318c11dca46c09b0bb;
}. Bridge is not set. This is probably because you've explicitly synthesized the bridge in EXReactNativeEventEmitter, even though it's inherited from RCTEventEmitter.'
*** First throw call stack:
(0x180a290fc 0x199279d64 0x1822e5504 0x1049681d4 0x10509e8d0 0x10509e184 0x104187614 0x1041d27a8 0x1041cae40 0x1041caca0 0x1041c8ebc 0x1041cad78 0x1854b6284 0x1041cab04 0x1041caeb4 0x108eb46d4 0x108eb63b4 0x108ec6898 0x1809e1d84 0x18099bf5c 0x1809af468 0x19c55338c 0x1833525d0 0x1830d0f74 0x1040bfa60 0x108bd1aa4)
libc++abi: terminating with uncaught exception of type NSException
dyld4 config: DYLD_LIBRARY_PATH=/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib:/Developer/Library/PrivateFrameworks/GPUTools.framework/libglInterpose.dylib
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: onDevicePushToken with body: {
    devicePushToken = 2f21636b42bd83caf8d4f79604849b79e0e36488bee3cf318c11dca46c09b0bb;
}. Bridge is not set. This is probably because you've explicitly synthesized the bridge in EXReactNativeEventEmitter, even though it's inherited from RCTEventEmitter.'
terminating with uncaught exception of type NSException

Just importing import * as Notifications from 'expo-notifications'; is enough for my app to crash with this error.

It got finally assigned to a dev, guess it will be fixed soon 😃

I dont understand why expo is not addressing this issue. It is really frustrating developer experience not being able to reload ios devices. It has been there for 4 months. It seriously kills the will to develop

I solved it by replacing all expo-notifications imports with this (which just mocks expo-notifications for iOS in dev-mode):

./expo-notifications.js
// eslint-disable-next-line no-undef, @typescript-eslint/no-var-requires, import/order
const { Platform } = require('react-native');

/** @type {typeof import('expo-notifications/src/index')} */
const mockedNotifications = {
  getDevicePushTokenAsync: (() => Promise.resolve({})),
  getExpoPushTokenAsync: (() => Promise.resolve({})),
  getPresentedNotificationsAsync: (() => Promise.resolve({})),
  presentNotificationAsync: (() => Promise.resolve({})),
  dismissNotificationAsync: (() => Promise.resolve({})),
  dismissAllNotificationsAsync: (() => Promise.resolve({})),
  getNotificationChannelsAsync: (() => Promise.resolve({})),
  getNotificationChannelAsync: (() => Promise.resolve({})),
  setNotificationChannelAsync: (() => Promise.resolve({})),
  deleteNotificationChannelAsync: (() => Promise.resolve({})),
  getNotificationChannelGroupsAsync: (() => Promise.resolve({})),
  getNotificationChannelGroupAsync: (() => Promise.resolve({})),
  setNotificationChannelGroupAsync: (() => Promise.resolve({})),
  deleteNotificationChannelGroupAsync: (() => Promise.resolve({})),
  getBadgeCountAsync: (() => Promise.resolve({})),
  setBadgeCountAsync: (() => Promise.resolve({})),
  getAllScheduledNotificationsAsync: (() => Promise.resolve({})),
  scheduleNotificationAsync: (() => Promise.resolve({})),
  cancelScheduledNotificationAsync: (() => Promise.resolve({})),
  cancelAllScheduledNotificationsAsync: (() => Promise.resolve({})),
  getNotificationCategoriesAsync: (() => Promise.resolve({})),
  setNotificationCategoryAsync: (() => Promise.resolve({})),
  deleteNotificationCategoryAsync: (() => Promise.resolve({})),
  getNextTriggerDateAsync: (() => Promise.resolve({})),
  useLastNotificationResponse: (() => Promise.resolve({})),
  setAutoServerRegistrationEnabledAsync: (() => Promise.resolve({})),
  registerTaskAsync: (() => Promise.resolve({})),
  unregisterTaskAsync: (() => Promise.resolve({})),
  addPushTokenListener: (() => Promise.resolve({ remove: () => {} })),
  removePushTokenSubscription: (() => Promise.resolve({})),
  addNotificationReceivedListener: (() => Promise.resolve({ remove: () => {} })),
  addNotificationsDroppedListener: (() => Promise.resolve({ remove: () => {} })),
  addNotificationResponseReceivedListener: (() => Promise.resolve({ remove: () => {} })),
  removeNotificationSubscription: (() => Promise.resolve({})),
  getLastNotificationResponseAsync: (() => Promise.resolve({})),
  setNotificationHandler: (() => Promise.resolve({})),
  getPermissionsAsync: (() => Promise.resolve({})),
  requestPermissionsAsync: (() => Promise.resolve({})),
  usePermissions: (() => Promise.resolve({})),
  AndroidAudioContentType: {},
  AndroidAudioUsage: {},
  AndroidImportance: {},
  AndroidNotificationPriority: {},
  AndroidNotificationVisibility: {},
  DEFAULT_ACTION_IDENTIFIER: 'DEFAULT_ACTION_IDENTIFIER',
  IosAlertStyle: {},
  IosAllowsPreviews: {},
  IosAuthorizationStatus: {},
  NotificationTimeoutError: {},
};

// eslint-disable-next-line no-undef
const toExport = __DEV__ && Platform.OS === 'ios' ? { ...mockedNotifications, default: mockedNotifications } : require('expo-notifications');
// eslint-disable-next-line no-undef, @typescript-eslint/no-var-requires
// const toExport = require('expo-notifications');

/** @type {typeof import('expo-notifications/src/index')} */
module.exports = toExport; // eslint-disable-line functional/immutable-data, @typescript-eslint/no-var-requires

Just save it as a file locally and import from this instead of directly from expo-notifications. It mocks it specifically for iOS in dev mode so it doesn’t crash (but is also obviously not usable) - in all other cases it uses expo-notifications as usual. Hope this can help someone until it’s fixed! 👍

still an issue with the latest versions of expo-notifications (0.14.1) and expo-dev-client (0.8.4)

hang tight, or look at https://github.com/expo/expo/issues/15788#issuecomment-1099072403 and possibly try applying that suggestion to your project

I am also having this issue on iOS using the dev client.

ah yeah i mean it’s just do not import if on iOS and __DEV__

import { Platform } from 'react-native';

// HACK: shim all `expo-notifcation` calls as it breaks development for iOS.
// https://github.com/expo/expo/issues/15788
const IOS_NOTIFICATION_ISSUE = Platform.OS === 'ios' && __DEV__;

export let setNotificationHandler = (...args: any[]) => null;
...
if (!IOS_NOTIFICATION_ISSUE)  {
  const ExpoNotifications = require('expo-notifications');
  setNotificationHandler = ExpoNotifications.setNotificationHandler;
  ...
}

Seems to be fixed in 0.14.1 Edit: it’s not fixed.

@hirbod good point! I found the issue, that was on our side. Thank you! It works in production

Just to verify that, this exactly is the same problem which occurs only after the reload, but for me it is about onHandleNotification probably because I’m not using onDevicePushToken so yeah this is the same issue.

And it’s really serious!