React-native-firebase: App crash (Cloud Storage)

Created on 13 Oct 2019  路  26Comments  路  Source: invertase/react-native-firebase

Sometimes I have app crashes. It's related with Cloud Storage. The app was crashed immediately after file upload.

Crash log:

Fatal Exception: NSInvalidArgumentException
0  CoreFoundation                 0x19d3a498c __exceptionPreprocess
1  libobjc.A.dylib                0x19d0cd0a4 objc_exception_throw
2  CoreFoundation                 0x19d3fa3f8 -[__NSCFString characterAtIndex:].cold.1
3  CoreFoundation                 0x19d40375c -[__NSPlaceholderDictionary initWithObjects:forKeys:count:].cold.5
4  CoreFoundation                 0x19d2915dc -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]
5  CoreFoundation                 0x19d283470 +[NSDictionary dictionaryWithObjects:forKeys:count:]
6  AirWork                        0x102ef75f8 +[RNFBStorageCommon getUploadTaskAsDictionary:] + 309 (RNFBStorageCommon.m:309)
7  AirWork                        0x102efd044 __84-[RNFBStorageModule addUploadTaskObservers:appDisplayName:taskId:resolver:rejecter:]_block_invoke + 456 (RNFBStorageModule.m:456)
8  AirWork                        0x102d7463c __50-[FIRStorageObservableTask fireHandlers:snapshot:]_block_invoke_2 + 211 (FIRStorageObservableTask.m:211)
9  libdispatch.dylib              0x19d071610 _dispatch_call_block_and_release
10 libdispatch.dylib              0x19d072184 _dispatch_client_callout
11 libdispatch.dylib              0x19d0241d0 _dispatch_main_queue_callback_4CF$VARIANT$mp
12 CoreFoundation                 0x19d3223c4 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
13 CoreFoundation                 0x19d31d3b8 __CFRunLoopRun
14 CoreFoundation                 0x19d31c8bc CFRunLoopRunSpecific
15 GraphicsServices               0x1a7188328 GSEventRunModal
16 UIKitCore                      0x1a13b26d4 UIApplicationMain
17 AirWork                        0x1025cad68 main + 14 (main.m:14)
18 libdyld.dylib                  0x19d1a7460
Bug P1 Storage >= 6

Most helpful comment

@zwily I got storageMetadata set to nil. After adding a check there, I don't see crash anymore.
RNFBStorageCommon.m (line 309)

return [@{
      @"bytesTransferred": @(task.progress.completedUnitCount),
      @"metadata": storageMetadata != nil ? storageMetadata : [NSNull null],
      @"state": state,
      @"totalBytes": @(task.progress.totalUnitCount)
  } mutableCopy];

All 26 comments

You have to follow the template, or we can't help

Same here.


Issue



Sometimes I have app crashes. It's related with Cloud Storage. The app was crashed immediately after file upload.


Project Files






iOS

Click To Expand

#### `ios/Podfile`: - [ ] I'm not using Pods - [x] I'm using Pods and my Podfile looks like:

platform :ios, '10.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

target 'Odoori' do
  # Pods for Odoori
  pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
  pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
  pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
  pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
  pod 'React', :path => '../node_modules/react-native/'
  pod 'React-Core', :path => '../node_modules/react-native/'
  pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
  pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
  pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
  pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
  pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
  pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
  pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
  pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
  pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
  pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
  pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
  pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'

  pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
  pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
  pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
  pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
  pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
  pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
  pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

  pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
  pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
  pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

  pod 'QBImagePickerController', :path => '../node_modules/react-native-image-crop-picker/ios/QBImagePicker/QBImagePickerController.podspec'
  pod 'GoogleIDFASupport', '~> 3.14.0'

  pod 'ViroReact', :path => '../node_modules/react-viro/ios/'
  pod 'ViroKit_static_lib', :path => '../node_modules/react-viro/ios/dist/ViroRenderer/static_lib'

  use_native_modules!

end
#### `AppDelegate.m`:
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

@import Firebase;

#import "AppDelegate.h"

#import <ViroReact/VRTBundleURLProvider.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  BOOL enterVrImmediately = YES;
  BOOL usingNgrok = YES;

  if ([FIRApp defaultApp] == nil) {
    [FIRApp configure];
  }

  if(enterVrImmediately) {
    NSURL *jsCodeLocation = nil;
#ifdef DEBUG
    if(usingNgrok) {
      VRTBundleURLProvider *bundleProvider = [[VRTBundleURLProvider alloc] init];
      jsCodeLocation = [bundleProvider jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
    }
#endif
    if(jsCodeLocation == nil) {
      jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
    }

    RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"Odoori"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
    rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    UIViewController *rootViewController = [UIViewController new];
    rootViewController.view = rootView;
    self.window.rootViewController = rootViewController;
    [self.window makeKeyAndVisible];
  }
  return YES;
}

@end


Environment

Click To Expand

Sometimes I have app crashes. It's related with Cloud Storage. The app was crashed immediately after file upload.

**`react-native info` output:**
System:
    OS: macOS 10.15
    CPU: (4) x64 Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz
    Memory: 597.55 MB / 12.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 12.12.0 - /usr/local/bin/node
    npm: 6.12.0 - /usr/local/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  SDKs:
    iOS SDK:
      Platforms: iOS 13.1, DriverKit 19.0, macOS 10.15, tvOS 13.0, watchOS 6.0
    Android SDK:
      API Levels: 23, 26, 27, 28, 29
      Build Tools: 27.0.3, 28.0.3, 29.0.0, 29.0.1, 29.0.2
      System Images: android-25 | Google APIs ARM EABI v7a, android-28 | Google Play Intel x86 Atom, android-29 | Google APIs Intel x86 Atom, android-29 | Google Play Intel x86 Atom
  IDEs:
    Android Studio: 3.5 AI-191.8026.42.35.5900203
    Xcode: 11.1/11A1027 - /usr/bin/xcodebuild
  npmPackages:
    react: 16.10.2 => 16.10.2
    react-native: 0.61.2 => 0.61.2
  npmGlobalPackages:
    react-native-cli: 2.0.1
    react-native-rename: 2.4.1
- **Platform that you're experiencing the issue on**: - [ ] iOS - [ ] Android - [x] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - `6.0.1` - **`Firebase` module(s) you're using that has the issue:** - `@react-native-firebase/storage` - **Are you using `TypeScript`?** - `No`

Thanks for adding more information @Brouilles - could you provide the code snippet that does the upload?

Yes sure. It's this code.

fcmUpload() {
        if (this.state.messageImageSourceCrop != '') { 
            this.setState({ showLoader: true });

            const metadata = { contentType: 'image/jpeg' }
            var imageRef = null

            var userid = ''
            Util.MultiGetData([keys.id, keys.name, keys.username, keys.description, keys.avatar, 'local'], TAG)
                .then((data) => {
                    userid = data[0][1]

                    var timeStamp = Math.floor(Date.now());
                    timeStamp = userid + '_' + timeStamp

                            //thumbnail
                            imageRef = storage().ref('/Thumbnails').child(timeStamp + ".jpg")
                            imageRef.putString(`${this.state.messageImageSourceCrop}`, 'base64', { contentType: 'image/jpeg' })
                                .then(uploadedFile => {
                                    return imageRef.getDownloadURL()
                                })
                                .then(() => { return imageRef.getDownloadURL() })
                                .then((url) => {

                                    if (this.state.videoUrl != '') {
                                        var thumbUri = url

                                        console.log("kk-> thumbUri-"+thumbUri)

                                        console.log("kk-> this.state.videoUrl-"+this.state.videoUrl )

                                        RNFS.readFile(this.state.videoUrl, 'base64').then(base64VideoString => {

                                            const video_metadata = { contentType: 'video/mp4' }
                                            const videoRef = firebase.storage().ref('/Videos').child(timeStamp + ".mp4")
                                            console.log("kk-> videoRef -> "+videoRef)
                                            // videoRef.put(this.state.videoUrl, video_metadata)
                                            videoRef.putString(`${base64VideoString}`, 'base64', { contentType: 'video/mp4' })
                                                .then(uploadedFile => {
                                                    return videoRef.getDownloadURL()
                                                })
                                                .then(() => { return videoRef.getDownloadURL() })
                                                .then((url) => {
                                                    this.getAddress(url, thumbUri)
                                                    // PubSub.publish('updateuser');
                                                }).catch(err => {
                                                    console.log(err)
                                                    this.setState({ showLoader: false });
                                                });
                                        });
                                    } else {
                                        var thumbUri = url

                                        imageRef = firebase.storage().ref('/Images').child(timeStamp + ".jpg")
                                        imageRef.putString(`${this.state.messageImageSourceCrop}`, 'base64', { contentType: 'image/jpeg' })
                                            .then(uploadedFile => {
                                                return imageRef.getDownloadURL()
                                            })
                                            .then(() => { return imageRef.getDownloadURL() })
                                            .then((url) => {
                                                this.getAddress(url, thumbUri)
                                            }).catch(err => {
                                                this.setState({ showLoader: false });
                                            });
                                    }


                                }).catch(err => {
                                    console.log(err)
                                    this.setState({ showLoader: false });
                                });


                });
        } else {
            this.getAddress("")
        }
    }

messageImageSourceCrop is a base64 string.

But the app don't crash on iPhone XS. But others (iPhone 6s, iPhone 7 Plus, 7 sure). Maybe RAM? The image in Base64 is too big?

iOS 13.1.3. I don't have device on iOS 12.

But the app don't crash on iPhone XS. But others (iPhone 6s, iPhone 7 Plus, 7 sure). Maybe RAM? The image in Base64 is too big?

Possibly. Try various image sizes?

I have just try it. Same with small images, it's crash sometimes (most of the time).

I use putString because with the following code imageRef.put(Platform.OS === 'ios' ? imagePath.replace('file://', '') : imagePath, { contentType: 'image/jpeg' }) (uri is the local path). I have the follow error on all devices Error: 'RNFirebase.Base64.fromData' failed: Unknown data type.. Don't crash the app, but don't work.

Thanks.

If you need more information. I can help. Maybe, the code is not good? It's a simple conversion from the old version of RNFirebase. Tomorrow (France) I try on Android. I'll keep you informed.

Hello @igor-lemon
Have you find a solution?
I have the answer for Android soon. If it's working or not.

Hi guys,
Same issue here with the same config.
The app crashes when we upload pictures and videos from gallery or camera.
@igor-lemon Did you find a solution to workaround the issue?
Thanks a lot.

@Brouilles @FlrnOdr Hi guys! I don't know why you ask me about a solution. I just reported about the bug, I'm not RNFirebase dev.

Same problem here. The crash is only happening on older devices (like iPhone 6S, I've never seen it on my iPhone 11). My upload code is simpler:

    const path = `/u/${firebase.auth().currentUser.uid}/${uuid.v4()}`;
    const ref = firebase
      .storage()
      .ref()
      .child(path);

    const task = ref.putFile(localPath, {
      cacheControl: 'public, max-age=31536000',
    });

    await task.then();

I downgrade to 5.5.6 and it's working. I did not find any other solution.

@Brouilles I downgraded lib to 5.x too.

I don't have a device to test on, but staring at code it looks like the only way this can happen is if storageMetadata gets set to nil here: https://github.com/invertase/react-native-firebase/blob/master/packages/storage/ios/RNFBStorage/RNFBStorageCommon.m#L302

Putting a guard around that should be easy, but the bigger question is, why is that only coming up as nil on some older devices? I always set metadata on my file uploads, so it seems like there might be a race condition where the metadata is set asynchronously, and that condition only gets tickled on slower devices.

I could not replicate the crash in the simulator.

Also watch out with logging huge amounts of data... It puts a lot of stress on your application.

Also you should not send base64 data over the javascript core, it is not meant for that.

Rather send the location of the file.

:)

@unicornRainbows Like I said before:

I use putString because with the following code imageRef.put(Platform.OS === 'ios' ? imagePath.replace('file://', '') : imagePath, { contentType: 'image/jpeg' }) (uri is the local path). I have the follow error on all devices Error: 'RNFirebase.Base64.fromData' failed: Unknown data type.. Don't crash the app, but don't work.

@Brouilles putString is to put the string contents of a file, not a string file path. You should be using putFile if you have a file path/uri: https://invertase.io/oss/react-native-firebase/v6/storage/reference/reference#putFile

I have the same issue: sometimes app crash after file uploaded (I upload two same images in a row for test) from local storage to cloud storage.
My code for uploading:
````
uploadImageWithProgressBar(image, progressCallback) {
// define file name
const fileName = this.imageName(image);

// set firebase image reference
let fileImageRef = this.imagesRef.child(fileName);
// init upload task
const uploadTask = fileImageRef.putFile(image.path, { contentType: image.mime });

return new Promise((resolve, reject) => {
  let fullPath = '';
  const unsubscribe = uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, {
    next: (snapshot) => {
      // calculate upload progress in percent
      const uploadProgress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
      // report progress to callback
      progressCallback(uploadProgress);
      // check whether we finished uploading
      if (snapshot.state === 'success') {
        // compose image object
        fullPath = snapshot.ref.fullPath;
      }
    },
    error: (e) => {
      log.error(e);
      // unsubscribe from uploading task
      unsubscribe();
      // reject error
      reject(e);
    },
    complete: () => {
      // unsubscribe from uploading task
      unsubscribe();
      storage()
        .ref(fullPath)
        .getDownloadURL()
        .then((url) => {
          // add image properties
          image = {
            name: fileName,
            // set download url
            source: url,
          };
          // return new image object
          resolve(image);
        });
    },
  });
});

}

imageName(image) {
switch (image.mime) {
case 'image/jpeg':
return ${uuid.v1()}.jpg;

  case 'image/png':
    return `${uuid.v1()}.png`;

  default:
    log.warn(`unknown mime type: ${image.mime}`);
    return null;
}

}

async uploadImages() {
for (const image of [...this.images]) {
console.log('upload image');
await this.uploadImageWithProgressBar(image, (progress) => {
this.uploadProgress = progress;
});
console.log('upladed image');
}
````
What's happening:

  1. I select two files from local storage for uploading (both files are same every time)
  2. I start to upload first image (in a for-of loop)
  3. I see upload progress is changing from 0% to 100%
  4. I see console.log message that image was uploaded
  5. I see console.log message that second image is going to start uploading
  6. App crash

Error message from xcode:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]'

xcode screenshot

Steps to reproduce:

  1. Upload several images from local storage in a row
  2. App crashed? No -> Repeat 1. Yes -> Reproduced.

Device info:
iPhone XR (iOS 13.1.3, device)
iPhone 11 (iOS 13.2, simulator)

Ready to provide additional details.

p.s. part of package.json dependencies regarding react-native-firebase
... "@react-native-firebase/admob": "^6.0.3", "@react-native-firebase/app": "^6.0.3", "@react-native-firebase/auth": "^6.0.3", "@react-native-firebase/dynamic-links": "^6.0.3", "@react-native-firebase/messaging": "^6.0.3", "@react-native-firebase/remote-config": "^6.0.3", "@react-native-firebase/storage": "^6.0.3", ...

@zwily I got storageMetadata set to nil. After adding a check there, I don't see crash anymore.
RNFBStorageCommon.m (line 309)

return [@{
      @"bytesTransferred": @(task.progress.completedUnitCount),
      @"metadata": storageMetadata != nil ? storageMetadata : [NSNull null],
      @"state": state,
      @"totalBytes": @(task.progress.totalUnitCount)
  } mutableCopy];

@r0b0t3d good find, can you send a quick PR for that? I can get it out in a patch release today if so. Thanks

@Salakar PR created #2875

Fix is now live in v6.0.4. Thanks

I still get this error if I upload a really long filename as ref ID. Solved by creating my own ID.

import storage from '@react-native-firebase/storage';
import React, {useState} from 'react';
import {
  Alert,
  Button,
  Image,
  ImageURISource,
  StyleSheet,
  View,
} from 'react-native';
import ImagePicker, {ImagePickerOptions} from 'react-native-image-picker';

import {Colors} from '../Styles';
import {ErrorBoundary} from './ErrorBoundary';
import {v4 as uuid} from 'uuid';

type StorageImageProps = {
  onImage: (uri: string) => void;
};

export const StorageImage: React.FunctionComponent<StorageImageProps> = (
  props,
) => {
  const [image, setImage] = useState<ImageURISource | undefined>(undefined);
  const [uploading, setUploading] = useState<boolean>(false);
  const [progress, setProgress] = useState<number>(0);
  const imageOptions: ImagePickerOptions = {
    title: 'Select Image',
    allowsEditing: true,
    quality: 0.8,
    tintColor: Colors.AccentMain,
    cameraType: 'back',
    storageOptions: {
      skipBackup: true,
      path: 'images',
    },
  };
  const pickImage = () => {
    ImagePicker.showImagePicker(imageOptions, (r) => {
      if (r.didCancel) {
        return;
      }
      if (r.error) {
        Alert.alert('Could not select image', r.error, [
          {text: 'Close', onPress: () => {}},
        ]);
        return;
      }
      const imageSource: ImageURISource = {uri: r.uri};
      console.log('Created image source', imageSource);
      setImage(imageSource);
      uploadImage(imageSource);
    });
  };
  const uploadImage = async (imageSource: ImageURISource) => {
    if (!imageSource || !imageSource.uri || uploading) {
      console.log('Already uploading or no image', image);
      return;
    }
    setUploading(true);
    try {
      console.log('Uploading image');
      const newId = uuid();

      // Extract image extension
      const ext = imageSource.uri.split('.').pop();

      // Generate a safe filename
      const filename = `${newId}.${ext}`;

      await storage()
        .ref(filename)
        .putFile(imageSource.uri)
        .on('state_changed', async (snapshot) => {
          if (snapshot.error) {
            console.log('Upload failed', snapshot.error);
            return;
          }
          const newProgress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          setProgress(newProgress);
          if (snapshot.state === storage.TaskState.SUCCESS) {
            console.log('Upload success!');
            setProgress(0);
            setUploading(false);
            const liveUrl = await snapshot.ref.getDownloadURL();
            setImage({uri: liveUrl});
            props.onImage(liveUrl);
          }
        });
    } catch (error) {
      console.log('Upload failed', error);
      setProgress(0);
      setUploading(false);
    }
  };
  return (
    <ErrorBoundary>
      <View>
        {image ? (
          <Image source={image} style={{height: 200, margin: 5}} />
        ) : null}
        {uploading ? (
          <View style={[styles.progressBar, {width: `${progress}%`}]} />
        ) : null}
        <Button title="Pick image" onPress={() => pickImage()} />
      </View>
    </ErrorBoundary>
  );
};

const styles = StyleSheet.create({
  progressBar: {
    backgroundColor: 'rgb(3, 154, 229)',
    height: 3,
    shadowColor: '#000',
    margin: 5,
  },
});

@thomashagstrom can you open a new issue, this one is approximately a year old? It would be especially helpful to have an example of an actual file name that crashes

Was this page helpful?
0 / 5 - 0 ratings