Expo SDK 46
iOS and Android
Hi, I’ve recently been tasked with updating our app from SDK 43 to 46, primarily because the Google Play Store requires use to target new APIs.
This seemed to go well, although I spent several days resolving dependencies and finding suitable replacements for outdated/abandoned packages. I also had to use patch-package to patch several dependencies that didn’t not have suitable replacements or would require a prohibitive amount of work to upgrade.
The resultant app works well in Expo Go. I’ve also tried starting it locally with npx expo start --no-dev --minify
to mimic production conditions. I’m able to use all features of the app and there are a couple known issues with VirtualLists, but other than that, everything works.
We use eas to build and submit and both of these are working as well, with small tweaks to to our Gitlab pipelines to support some of the newer features available (–auto-submit, for instance).
However, once we get the app in stores and try to test out the builds installed from the stores, the app crashes after the splash screen. The error in Android is:
FATAL EXCEPTION: expo-updates-error-recovery
Process: com.asdf.app, PID: 20543
com.facebook.react.common.JavascriptException: TypeError: Cannot read property 'useRef' of null
This error is located at:
in NavigationContainer
in MainNavigator
in UiProvider
in DataProvider
in AppProvider
in RCTView
in Unknown
in Root
in Styled(Root)
in StyleProvider
in App
in ExpoRoot
in RCTView
in Unknown
in RCTView
in Unknown
in AppContainer, js engine: hermes, stack:
anonymous@1:1581999
NavigationContainer@1:1577145
renderWithHooks@1:111098
updateForwardRef@1:118844
beginWork$1@1:149508
performUnitOfWork@1:136944
workLoopSync@1:136847
renderRootSync@1:136731
performSyncWorkOnRoot@1:134229
flushSyncCallbacks@1:101389
scheduleUpdateOnFiber@1:131618
updateContainer@1:141971
anonymous@1:151298
renderApplication@1:498422
run@1:492802
runApplication@1:493244
__callFunction@1:164191
anonymous@1:162816
__guard@1:163757
callFunctionReturnFlushedQueue@1:162774
at com.facebook.react.modules.core.ExceptionsManagerModule.reportException(ExceptionsManagerModule.java:72)
at java.lang.reflect.Method.invoke(Native Method)
at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:188)
at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:228)
at java.lang.Thread.run(Thread.java:920)
Our package.json:
{
"name": "asdf",
"version": "46.12.0",
"description": "asdf",
"author": "asdf",
"homepage": "asdf",
"repository": {
"type": "git",
"url": "asdf"
},
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "npx expo start",
"build": "npx eas-cli build --platform all",
"submit": "npx eas-cli submit --latest --platform all",
"android": "npx expo start --android",
"ios": "npx expo start --ios",
"web": "npx expo start --web",
"eject": "npx expo eject",
"test": "yarn jest --clearMocks --coverage",
"test-update": "yarn jest --clearMocks -u",
"test-watch": "yarn jest --clearMocks --watch",
"postinstall": "patch-package"
},
"dependencies": {
"@codler/react-native-keyboard-aware-scroll-view": "^2.0.1",
"@expo/config-plugins": "^5.0.1",
"@expo/react-native-action-sheet": "^3.13.0",
"@expo/vector-icons": "^13.0.0",
"@expo/webpack-config": "^0.17.2",
"@react-native-async-storage/async-storage": "~1.17.10",
"@react-native-community/datetimepicker": "^6.2.0",
"@react-native-community/progress-bar-android": "^1.0.4",
"@react-native-community/progress-view": "^1.3.2",
"@react-native-masked-view/masked-view": "^0.2.7",
"@react-native-picker/picker": "^2.4.4",
"@react-navigation/bottom-tabs": "^5.11.15",
"@react-navigation/core": "^5.16.1",
"@react-navigation/drawer": "^5.12.9",
"@react-navigation/native": "^5.9.8",
"@react-navigation/stack": "^5.14.9",
"@sentry/react-native": "4.2.2",
"axios": "^0.27.2",
"babel-plugin-inline-dotenv": "^1.7.0",
"color": "^3.2.0",
"dayjs": "^1.11.5",
"deprecated-react-native-prop-types": "^2.3.0",
"expo": "^46.0.10",
"expo-app-loading": "~2.1.0",
"expo-application": "~4.2.2",
"expo-constants": "~13.2.3",
"expo-device": "~4.3.0",
"expo-firebase-analytics": "~7.1.1",
"expo-firebase-core": "~5.1.1",
"expo-font": "~10.2.0",
"expo-image-picker": "~13.3.1",
"expo-notifications": "~0.16.1",
"expo-postpublish-slack-notify": "^1.2.0",
"expo-status-bar": "~1.4.0",
"expo-updates": "~0.14.5",
"lodash": "^4.17.21",
"metro-minify-terser": "^0.63.0",
"moment": "^2.29.4",
"moment-timezone": "^0.5.37",
"native-base": "^2.15.2",
"patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native": "^0.69.5",
"react-native-communications": "^2.2.1",
"react-native-gesture-handler": "~2.5.0",
"react-native-gifted-chat": "^1.0.4",
"react-native-lightbox": "^0.8.1",
"react-native-maps": "^0.31.1",
"react-native-modal": "^11.6.1",
"react-native-parsed-text": "^0.0.22",
"react-native-reanimated": "~2.9.1",
"react-native-safe-area-context": "^4.3.1",
"react-native-screens": "~3.15.0",
"react-native-snap-carousel": "^3.9.1",
"react-native-typing-animation": "^0.1.7",
"react-native-web": "~0.18.7",
"react-native-webview": "^11.23.0",
"sentry-expo": "~5.0.2",
"use-debounce": "^5.1.0"
},
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/plugin-transform-react-jsx-source": "^7.9.0",
"@babel/preset-env": "^7.18.10",
"@testing-library/jest-native": "^3.4.3",
"@testing-library/react-native": "^7.1.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.2",
"babel-plugin-module-resolver": "^4.0.0",
"babel-preset-expo": "~9.2.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-plugin-flowtype": "^4.7.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-react-native": "^3.8.1",
"flow-bin": "^0.186.0",
"flow-typed": "^3.8.0",
"husky": "^4.2.3",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest-expo": "^46.0.1",
"jest-html-reporter": "^3.6.0",
"jest-junit": "^14.0.1",
"jest-junit-reporter": "^1.1.0",
"jest-mock-axios": "^4.6.1",
"pre-commit": "^1.2.2",
"prettier": "^2.7.1",
"react-test-renderer": "^18.2.0",
"standard-version": "^9.5.0",
"standard-version-expo": "^1.0.3"
},
"resolutions": {
"@codler/react-native-keyboard-aware-scroll-view": "2.0.1",
"@expo/config-plugins": "5.0.1",
"@react-native-community/datetimepicker": "6.2.0",
"@react-native-picker/picker": "2.4.4",
"react": "18.2.0"
},
"private": true
}
It appears as if the error is occurring in our MainNavigator:
import React, { useRef } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import * as Analytics from 'expo-firebase-analytics';
import SignUp from '_screens/Auth/SignUp';
import Splash from '_screens/Auth/Splash';
import Login from '_screens/Auth/Login';
import ForgotPassword from '_screens/Auth/ForgotPassword';
import DrawerNavigator from '_navigators/DrawerNavigator';
import EventList from '_screens/Event/EventList';
import InAppBrowserScene from '_screens/InAppBrowser';
const MainStack = createStackNavigator();
/**
* Main App Navigator
* Contains:
* Auth screens
* Event List screen
* Main Drawer screen (Home)
* InAppBrowser screen
*
* @returns {JSX.Element}
* @constructor
*/
const MainNavigator = () => {
const routeNameRef = useRef();
const navigationRef = useRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.current.getCurrentRoute().name;
}}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current.getCurrentRoute().name;
if (previousRouteName !== currentRouteName) {
await Analytics.logEvent('screen_view', {
firebase_screen: currentRouteName,
firebase_screen_class: currentRouteName
});
}
routeNameRef.current = currentRouteName;
}}
>
<MainStack.Navigator initialRouteName="Splash" headerMode="none">
<MainStack.Screen name="Splash" component={Splash} />
<MainStack.Screen name="EventList" component={EventList} options={{ unmountOnBlur: true }} />
<MainStack.Screen name="Login" component={Login} options={{ unmountOnBlur: true }} />
<MainStack.Screen name="Drawer" component={DrawerNavigator} options={{ unmountOnBlur: true }} />
<MainStack.Screen name="SignUp" component={SignUp} />
<MainStack.Screen name="ForgotPassword" component={ForgotPassword} />
<MainStack.Screen name="InAppBrowser" component={InAppBrowserScene} options={{ unmountOnBlur: true }} />
</MainStack.Navigator>
</NavigationContainer>
);
}
export default MainNavigator;
But even removing the code that calls useRef() (and the associated analytics code), the error persists.
Our App.js, in case it is useful:
import React from 'react';
import 'react-native-gesture-handler';
import { Root, StyleProvider } from 'native-base';
import getTheme from '_theme/components';
import variables from '_theme/variables/commonColor';
import MainNavigator from '_navigators/MainNavigator';
import { AppProvider } from '_contexts/AppContext';
import { DataProvider } from '_contexts/DataContext';
import { UiProvider } from '_contexts/UiContext';
import PushNotifications from '_components/PushNotifications';
import * as Sentry from 'sentry-expo';
Sentry.init({
dsn: process.env.SENTRY_DSN,
enableInExpoDevelopment: true,
debug: true
});
const App = () => {
return (
<StyleProvider style={getTheme(variables)}>
<Root>
<AppProvider>
<DataProvider>
<UiProvider>
<PushNotifications />
<MainNavigator />
</UiProvider>
</DataProvider>
</AppProvider>
</Root>
</StyleProvider>
);
};
export default App;
Debugging this has been time consuming- since Expo Go works fine, we have to build and submit and then either wait for the Play Store’s pre launch testing, or install locally and wait for the errors to start showing up in Sentry. Resolving the issue would be great, but at the very least, reproducing locally and being able to get better log information (non-bundled location and line numbers) could at least point us in the direction.
Does anyone have any ideas? I’ve maintained this project for a couple years and through a couple Expo SDK upgrades, but I’m reaching the end of my domain knowledge here. Thanks!