2
votes

I have been using ExpoKit to develop an application, which relies on usage of AsyncStorage, and everything worked nice until I wanted to deploy it to prod.

In my package.json I have: "react-native": "https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz" And iOS Deployment Target is set to 10.0 for both Pods and my project. I can provide more debugging info, if necessary, not sure what is relevant in this case.

For different reasons I would like not to use ExpoKit's default way hosting the bundle in production app, but would still like to keep expo's development tooling and therefore I changed the code of AppDelegate.m to this:

#import "AppDelegate.h"
#import "ExpoKit.h"
#import "EXViewController.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@interface AppDelegate ()

@property (nonatomic, strong) EXViewController *rootViewControllerExpo;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    #ifdef DEBUG
        // Expo
        [[ExpoKit sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
        _rootViewControllerExpo = [ExpoKit sharedInstance].rootViewController;

        _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        _window.backgroundColor = [UIColor whiteColor];
        _window.rootViewController = _rootViewControllerExpo;
        [_window makeKeyAndVisible];
    #else
        // React Native
        NSURL *jsCodeLocation;
        jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

        RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                            moduleName:@"RentalCollectorsApp"
                                                     initialProperties:nil
                                                         launchOptions:launchOptions];

        UIViewController *rootViewController = [UIViewController new];
        rootViewController.view = rootView;

        _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        _window.backgroundColor = [UIColor whiteColor];
        _window.rootViewController = rootViewController;
        [_window makeKeyAndVisible];

        //        UIView* launchScreenView = [[[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil] objectAtIndex:0];
        //        launchScreenView.frame = self.window.bounds;
        //        rootView.loadingView = launchScreenView;
    #endif

    return YES;
}

And then my app, which was working totally fine with ExpoKit, started to crash upon opening when executing a release scheme with the following message. It works fine in debug mode when using Expo's rootViewController

[error][tid:com.facebook.react.JavaScript] undefined is not an object (evaluating 'l.multiMerge')
[fatal][tid:com.facebook.react.ExceptionsManagerQueue] Unhandled JS Exception: undefined is not an object (evaluating 'l.multiMerge')
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

After hours of debugging I have pinpointed the minimal case which causes the crash quite consistently. Importing AsyncStorage by itself does not crash the app, but referencing it in a variable immediately causes the crash:

import React, { Component } from 'react';
import { Text, View, AsyncStorage } from 'react-native';

export default class AppContainer extends Component {
  render() {
    const bar = AsyncStorage;
    // const bar = {
    //   storage: AsyncStorage,
    // };

    return (
      <View
        style={{
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: 'yellow',
        }}
      >
        <Text>Hello!</Text>
      </View>
    );
  }
}

In my particular case crash is caused by using redux-persist, but it seems that the problem is wider, since when I remove initialisation of redux-persist it starts to crash somewhere in the middle of react-native-mapbox-gl. Just for reference, code of redux-persist https://github.com/rt2zz/redux-persist/blob/master/src/storage/index.native.js

Googling gave me some results, but no answers:

Undefined is not an object (evaluating 'RCTAsyncStorage.multiMerge' (React-native-asyncstorage)

https://github.com/facebook/react-native/issues/21948

Any help is appreciated.

2

2 Answers

0
votes

You should not be assigning AsyncStorage directly to any variable. If the user enables caching, to set an item in AsyncStorage do the following:

import AsyncStorage from 'react-native';
const persistTest = {
   AsyncStorage
};

persistTest.AsyncStorage.setItem("testCache", "Y");

and to retrieve this and continue:

persistTest.AsyncStorage.getItem("testCache").then(res => {
  this.continueTest(res);
});

I am not 100% sure if this answers your question because i did not follow everything fully in your question, but this is just from my own experience. If i can help further let me know. :)

0
votes

To whoever is reading this and struggling with similar issues, eventually after tearing my hair on my head for three-something days we ended up ejecting from Expo completely on the native side. Basically we bootstrapped vanilla without-expo RN project, moved all the code there and magically everything started to work.

Main reason, we assume, what was happening is that certain native libs have been conflicting with each other and accessing certain APIs caused runtime crash, which we have experienced.

Good news is that we have now 4 RN apps in production, and this whole expo-mess is behind us for good.

self-promotion-start

Hit me up in some twitters by the same handle if you would be interested to work on something like this. We are doing cool things with React Native in Bolt.

self-promotion-end