Expo iOS13 crashes instantly on startup

We had this issue and it seemed to be mostly iPhone Xr and Xs models, but only when they had “Guided Access” turned on. Once the users turned that off it resolved it for all of them.

Interesting! I’ll try to ask one of the users we know is experiencing the issue to see if this is the case for her :+1: I’ll post back here when I know

No dice, unfortunately

Thanks @clwendt88 I think this was our issue too! I can enable guided access on my phone and reproduce the crash.

Is this a new feature in ios13 that expo doesn’t fully support yet?

I don’t think it’s a new iOS feature, but it seemed to start causing an issue with iOS 13 on certain devices. There were only a few users that had it turned on, so we didn’t dig in to find a fix that resolves the crash while it’s turned on. (we just had those users turn it off)

Any advice about how to proceed here? According to the Expo Docs on Error Handling, there’s no way for me to gather native crash logs on iOS from my users, and I can’t reproduce it myself with neither simulator nor the two physical iPhones I have available. @esamelson

I’m experiencing this same issue. It happens on a completely fresh simulator install. The simulator has no Guided Access either.

We’ve combed through device logs, and were able to find this:

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]’
*** First throw call stack:
(
	0  CoreFoundation           0x0000000111ebe1ab __exceptionPreprocess + 171
	1  libobjc.A.dylib           0x0000000110ad6f41 objc_exception_throw + 48
	2  CoreFoundation           0x0000000111efdf9c _CFThrowFormattedException + 194
	3  CoreFoundation           0x0000000111dd2b41 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 321
	4  CoreFoundation           0x0000000111dd29cb +[NSDictionary dictionaryWithObjects:forKeys:count:] + 59
	5  Exponent              0x00000001041bb94b __41-[SEGIntegrationsManager refreshSettings]_block_invoke_3 + 208
	6  Exponent              0x00000001041b6cbe __seg_dispatch_specific_block_invoke + 25
	7  libdispatch.dylib          0x000000<…>

Would appreciate any insight anyone has, because our development is at a standstill.

is this happening on a brand new project too?

are you using Segment in your project?

Yep. We are using Segment, but we’ve sorted it out.

For us, this issue was due to a missing entry in our config file, being referenced by Segment. I assume that’s what the attempt to insert nil object from objects[0] is all about.

If anyone else is experiencing this kind of error, and it’s not due to Guided Access, it’s a good idea to check and make sure values are being passed to native libraries properly.

1 Like

Just out of curiosity, what was the missing entry @t3rminus ?

In our case, we were initializing segment like this:

Segment.initialize({ androidWriteKey: config.SEGMENT_ANDROID, iosWriteKey: config.SEGMENT_IOS });

but config.SEGMENT_ANDROID and config.SEGMENT_IOS were both undefined. This was causing the app to crash instantly on launch, with no error messages, other than ones buried in the device’s system logs.

TL;DR We get a handful of “Cannot download [assetlink on Cloudfront]” errors on Sentry, leading me to think the crash might have something to do with the asset caching code done during the AppLoading screen - might there be some issue for clean installs and assets provided OTA (that are not a part of a binary?)

It occurred to me recently that since the user’s did get to see our AppLoading splash, that the error might be related to the asset caching code that runs while it shows. It’s pretty much copy+paste from the guide in the docs, but I’m running out of ideas.

A short summary here:

AppContainer.tsx

const loadAssets = async () => await loadAndCacheAssets();

  if (!isReady) {
    return (
      <GenericErrorBoundary prefix="AppLoading">
        <AppLoading startAsync={loadAssets} onFinish={() => setIsReady(true)} onError={handleLoadingError} />
      </GenericErrorBoundary>
    );
  }

// else return full app
export const loadAndCacheAssets = async () => {
  try {
    // Cache bundled image assets
    const imageAssets = await cacheImages(getAllImagesFromAssets());
    // Cache remote fonts and font icons
    const fontAssets = cacheFonts([
      FontAwesome.font,
      // ...other fonts
    ]);

    // Download and cache locally bundled fonts
    const loadFonts = Font.loadAsync(require('./../assets/fonts/RobotoRegular.ttf'));
    await Promise.all([...imageAssets, ...fontAssets, loadFonts]);
  } catch (error) {
    console.log(`Error loading assets\n${error}`);
    captureException(error);
  }
};

const cacheImages = async (images: any[]) => {
  return await images.map(async (image: any) => {
    if (typeof image === 'string') {
      return Image.prefetch(image);
    } else {
      try {
        return await Asset.fromModule(image).downloadAsync();
      } catch (e) {
        console.warn(`There was an error downloading an image. The error was: ${e}`);
        captureException(e);
      }
    }
  });
};

const cacheFonts = (fonts: any) => {
  return fonts.map((font: any) => Font.loadAsync(font));
};

const getAllImagesFromAssets = () => {
  const appIcons = Object.keys(Images.appIcons).map(key => Images.appIcons[key]);
  const backgrounds = Object.keys(Images.backgrounds).map(key => Images.backgrounds[key]);
  const images = Object.keys(Images.images).map(key => Images.images[key]);
  const tabBarIcons = Object.keys(Images.tabbarIcons).map(key => Images.tabbarIcons[key]);
  return [...appIcons, ...backgrounds, ...images, ...tabBarIcons, Images.curvedArrow, Images.pushPermissionButton];
};

Images.ts

export const Images = {
  appIcons: {
    shopIcon: require('./../assets/icons/shop-icon.png'),
    // ... other icons
  },
  images: {
    wink: require('./../assets/images/wink.gif'),
    // more images
  },
  // and some more...
};

I then found some errors on Sentry that might confirm my suspicion - a lot of

Could not download from 'https://d1wp6m56sqw74a.cloudfront.net/~assets/1bc5a7992af35c03a9323043b2efe23a

where the assets vary, but are all some of the assets that are bundled with the app.

The issue seems to mostly affect new (re)-installs of the app - might there be some issue with OTA updates for assets?

@esamelson - I think this might be the closest thing to a stack trace I can provide. Any thoughts about my theory that the crash could be due to asset caching, and OTA provided assets might have some issues being delivered to fresh binary installs?

This makes me sad because my app does a similar crash and I just installed Sentry thinking it would be able to catch it, haha.

2 Likes

For me the issue was also “Guided Access” turned on.

Hi

It’s difficult to say for sure without proper error messages and stack traces, but there might be different causes of some of the crashes in this thread. e.g. I doubt Guided Access has anything to do with the inability to download assets.

I have seen crashes on Android where the error message is “Unexpected end of script” and I believe that’s related to incomplete downloads of the JS bundle which happens by default when the app first starts. If you’re seeing “Could not download …” for assets then maybe your users are also sometimes getting partially downloaded JS bundles. When the app starts next it will try to run the code and find it truncated, causing a crash and an “Unexpected end of script” error.

Here’s one of my posts about this on Android with a workaround that seems to have fixed it for me (although I don’t have an app on the App Store/Play Store, and hardly any users, so I can’t say for sure):

Trying to keep this open. On SDK 34, iOS 13.3.1. users on XR/XS phones reporting crashes. Phone basically makes it to splashscreen and crashes. Guided Access is not on. App runs fine on Expo.

Just had another report, we’re now up to ~10 users with the issue. It is incredibly frustrating, as my only remedy for them is “Try another device”. They are all iOS users with newer phones (typically iPhone X or newer), with updated OS’s and a freshly downloaded app. I’ve tried reproducing on our own phones with similar setups as well as multiple emulators, but with no luck.

I’ve resorted to doing a try/catch around the entire app, hoping that they would see a simple screen with some error specifics the next time we got a report, but they say that it still just crashes on the splashscreen:

import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';

import AppContainer from './src/AppContainer';
import GenericErrorBoundary from './src/components/common/GenericErrorBoundary';
import { configure } from './src/utils/sentryUtils';

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    flexGrow: 1,
    justifyContent: 'center',
    marginHorizontal: 8,
    marginTop: 100,
  },
  bottomView: { height: 150 },
});

const App: React.FC = () => {
  try {
    configure();
    return (
      <GenericErrorBoundary prefix="App root">
        <AppContainer />
      </GenericErrorBoundary>
    );
  } catch (error) {
    console.log(error);
    return (
      <ScrollView contentContainerStyle={styles.container}>
        <Text>Uh oh! App startup failed{'\n'}</Text>
        {/* eslint-disable-next-line */}
        <Text>It's not you, it's us{'\n\n'}</Text>
        <Text style={{ textAlign: 'center' }}>
          {/* eslint-disable-next-line */}
          Please contact hello@brandheroes.com with a screenshot of this error for help. We're very sorry for the
          inconvenience{'\n\n'}
        </Text>
        <Text>{`${error}`}</Text>
        <View style={styles.bottomView} />
      </ScrollView>
    );
  }
};

export default App;

The error view is comprised of simple vanilla react-native components. As far as I can tell, this confirms that the issue is not with our JS code, but with some binary integration.

Maybe it’s a key in our app.json that is not valid in some specific configuration of iOS?

{
  "expo": {
    "name": "Brandheroes",
    "description": "An app for Brandheroes",
    "slug": "brandheroes",
    "privacy": "unlisted",
    "sdkVersion": "35.0.0",
    "version": "2.3.2",
    "orientation": "portrait",
    "primaryColor": "#E1BEE7",
    "icon": "./assets/icons/android-icon.png",
    "scheme": "brandheroes",
    "assetBundlePatterns": ["assets/*"],
    "splash": {
      "image": "./assets/splash-mascot-only.png",
      "resizeMode": "cover",
      "backgroundColor": "#ffffff"
    },
    "androidStatusBar": {
      "barStyle": "dark-content",
      "backgroundColor": "#ffffff"
    },
    "extra": {
      "expoBuildNumber": "115"
    },
    "updates": {
      "fallbackToCacheTimeout": 15000
    },
    "ios": {
      "supportsTablet": false,
      "userInterfaceStyle": "automatic",
      "icon": "./assets/icons/iOS-icon.png",
      "bundleIdentifier": "com.brandheroes.ios",
      "buildNumber": "18",
      "config": {
        "usesNonExemptEncryption": false
      },
      "infoPlist": {
        "NSCameraUsageDescription": "The app needs permission to your camera in order to take photos for upload",
        "NSPhotoLibraryUsageDescription": "The app needs permission to your camera roll in order to add photos you've already taken for upload"
      }
    },
    "android": {
      "icon": "./assets/icons/android-icon.png",
      "googleServicesFile": "./google-services.json",
      "package": "com.brandheroes.android",
      "versionCode": 18,
      "permissions": ["CAMERA", "READ_EXTERNAL_STORAGE", "READ_INTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"]
    },
    "hooks": {
      "postPublish": [
        {
          "file": "sentry-expo/upload-sourcemaps",
          "config": {
            "organization": "brandheroes",
            "project": "brandheroes-app",
            "authToken": "[our auth token for Sentry]"
          }
        }
      ]
    }
  },
  "name": "brandheroes"
}

Any and all help or pointers would be very much appreciated. Besides having our users mail us their phone, I’m at a loss for where to go form here. @notbrent @esamelson - semidesparate ping :slight_smile:

As far as I can tell, it can’t be missing config keys or other undefineds as was the case for @t3rminus, as the app works fine for the vast majority of users, new and existing.

Is there any way for me to detect whether the issue is with a partial asset download, or for a partial app bundle download? I still suspect this might be it. I’m wondering why we get all these ‘cannot download asset from [cloudfront URL]’ errors, seeing as those assets should be bundled with the binary according to the docs?

For example, our icon for home, and a bunch of others: https://d1wp6m56sqw74a.cloudfront.net/~assets/f90d024e8baa4b45f16a14cf18a59ff9

can you create an issue on https://github.com/expo/expo/issues? thanks

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.