11

i am trying to send a recently in app - captured photo by mail and am encountering the following error:

(for the mailing functionality i am using this module : var Mailer = require('NativeModules').RNMail;

I am trying to send a photo by mail with the help of this module and get the following error:

index.ios.bundle:28842Exception '-[MFMailComposeInternalViewController addAttachmentData:mimeType:fileName:] attachment must not be nil.' was thrown while invoking mail on target RNMail with params (
        {
        attachment =         {
            name = Ladunek;
            path = "assets-library://asset/asset.JPG?id=3B7DBB2E-1271-4D86-A5F2-A0CEEE7CC4DE&ext=JPG";
            type = jpg;
        };
        body = "body";
        isHTML = 1;
        recipients =         (
            "placeholder@mail.com"
        );
        subject = Ladunek;
    },
    9
)

This is the invoking code :

.then((data, path) => {
        console.log(data)
        console.log(data.path)
        Mailer.mail({
          subject: 'Ladunek',
          recipients: ['placeholder@mail.com'],
          body: 'body',
          isHTML: true, // iOS only, exclude if false
          attachment: {
            path: data.path,  // The absolute path of the file from which to read data.
            type: 'jpg',   // Mime Type: jpg, png, doc, ppt, html, pdf
            name: 'Ladunek',   // Optional: Custom filename for attachment
          }
        }, (error, event) => {
            if(error) {
              AlertIOS.alert('Error', 'Could not send mail. Please send a mail to support@example.com');
            }
        });
      })

Is the path invalid? Or might it be something else.

EDIT

I am obtaining the file path with this module react-native-camera like so:

Event:

takePicture() {
    this.camera.capture()
      .then((data, path) =>

Element:

<Camera
              ref={(cam) => {
                this.camera = cam;
              }}
              style={{
                    flex: 1,
                    justifyContent: 'flex-end',
                    alignItems: 'center',
                    height: 400,
                    width: Dimensions.get('window').width
                  }}
              aspect={Camera.constants.Aspect.fill}>
              <Text style={{
                        flex: 0,
                        backgroundColor: '#fff',
                        borderRadius: 5,
                        color: '#000',
                        padding: 10,
                        margin: 40
                      }} onPress={this.takePicture.bind(this)}>{cameraIcon}</Text>
            </Camera>

UPDATE2

After including an obj-c file for uri to path transformation I am receiving the following error now:

ExceptionsManager.js:76 JSON value '<null>' of type NSNull cannot be converted to NSString

Did I "remove" the wrong lines from the following code? :/

Obj-c File content:

#import "RCTBridgeModule.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <UIKit/UIKit.h>
@interface ReadImageData : NSObject <RCTBridgeModule>
@end

@implementation ReadImageData

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(readImage:(NSString *)input callback:(RCTResponseSenderBlock)callback)
{

  // Create NSURL from uri
  NSURL *url = [[NSURL alloc] initWithString:input];

  // Create an ALAssetsLibrary instance. This provides access to the
  // videos and photos that are under the control of the Photos application.
  //ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

  // Using the ALAssetsLibrary instance and our NSURL object open the image.
  //[library assetForURL:url resultBlock:^(ALAsset *asset) {

    // Create an ALAssetRepresentation object using our asset
    // and turn it into a bitmap using the CGImageRef opaque type.
    //CGImageRef imageRef = [asset thumbnail];

    // Create UIImageJPEGRepresentation from CGImageRef
   // NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);

    // Convert to base64 encoded string
    // NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0];

    callback(@[url]);

  //} failureBlock:^(NSError *error) {
    //NSLog(@"that didn't work %@", error);
  //}];



}
@end
noa-dev
  • 3,561
  • 9
  • 34
  • 72

1 Answers1

8

------ EDITED ANSWER BELOW ------

Okay, so I finally have a Mac and was able to look into this issue in more details.

This is what I found for both Android and iOS.

The assumption is that you are using react-native-camera together with react-native-mail

- 1: Absolute Path

Add property captureTarget={Camera.constants.CaptureTarget.disk} to Camera component like so:

<Camera
  captureTarget={Camera.constants.CaptureTarget.disk}
  ref={(cam) => {
    this.camera = cam;
  }}
  style={styles.preview}
  aspect={Camera.constants.Aspect.fill}>
  <Text style={styles.capture} onPress={this.takePicture.bind(this)}>[CAPTURE]</Text>
</Camera>

Now camera component should return absolute file path instead of uri.

So for Android you should see something like this:

"file:///storage/emulated/0/Pictures/RCTCameraModule/IMG_20160730_060652.jpg"

instead of:

"content://media/external/images/media/86"

and for iOS you should get something like this:

"/Users/anton/Library/Developer/CoreSimulator/Devices/9A15F203-9A58-41C5-A4FC-EA25FAAE92BD/data/Containers/Data/Application/79FF93F9-BA89-4F4C-8809-277BEECD447D/Documents/EFFF0ECE-4063-4FE5-984E-E76506788350.jpg"

instead of:

"assets-library://asset/asset.JPG?id=0058FA4A-268F-408A-9150-017A3DA368D2&ext=JPG"

- 2: Pitfalls

iOS:

If Apple's MFMailComposeViewController crashes and you see the following error message:

enter image description here

This is most likely because you are running the app on iOS 9 Simulator. Solution: either test the app on real device, or download an older Simulator such as iOS 8.4.

More information on this issue can be found here

Android:

As of this writing there is no attachment support for Android.

Solution: (PR has been made to add this feature, but if you can't wait)

Add the following code to file RNMAILModule.java

if (options.hasKey("attachment") && !options.isNull("attachment")) {
  i.putExtra(Intent.EXTRA_STREAM, Uri.parse(options.getMap("attachment").getString("path")));
}

When both Android and iOS work you should have something like this:

enter image description here enter image description here

And here is the working code:

var Mailer = require('NativeModules').RNMail;
import Camera from 'react-native-camera';

import React, {Component} from 'react';
import
{
  View,
  TouchableHighlight,
  Text,
  StyleSheet,
  Dimensions,
  CameraRoll
}
from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  preview: {
    flex: 1,
    justifyContent: 'flex-end',
    alignItems: 'center',
    height: Dimensions.get('window').height,
    width: Dimensions.get('window').width
  },
  capture: {
    flex: 0,
    backgroundColor: '#fff',
    borderRadius: 5,
    color: '#000',
    padding: 10,
    margin: 40
  }
});

class SendPhoto extends Component {
  takePicture() {
    this.camera.capture()
      .then((data) => {
        console.log(data.path)
        Mailer.mail({
          subject: 'Ladunek',
          recipients: ['placeholder@mail.com'],
          body: 'body',
          isHTML: true, // iOS only, exclude if false
          attachment: {
            path: data.path,  // The absolute path of the file from which to read data.
            type: 'jpg',   // Mime Type: jpg, png, doc, ppt, html, pdf
            name: 'Ladunek',   // Optional: Custom filename for attachment
          }
        }, (error, event) => {
            if(error) {
              AlertIOS.alert('Error', 'Could not send mail. Please send a mail to support@example.com');
            }
        })
      })
      .catch(err => console.error(err));
  }

  render() {
    return(
      <View>
        <View style={styles.container}>
          <Camera
            captureTarget={Camera.constants.CaptureTarget.disk}
            ref={(cam) => {
              this.camera = cam;
            }}
            style={styles.preview}
            aspect={Camera.constants.Aspect.fill}>
            <Text style={styles.capture} onPress={this.takePicture.bind(this)}>[CAPTURE]</Text>
          </Camera>
        </View>
      </View>
    );
  }
}
export default SendPhoto;

------ OLD ANSWER BELOW ------

I never used this module before, but it looks like it expects absolute path of the file, however you are providing a file uri.

How are you obtaining this file uri?

Try using react-native-get-real-path module to see if it helps, you can find it here: react-native-get-real-path

i.e. convert your file uri to obtain real path, and use that as path

Community
  • 1
  • 1
Antoni4
  • 2,535
  • 24
  • 37
  • Thank you for your answer - I have udpated my question to answer your questions where I get the path from – noa-dev Jul 04 '16 at 20:45
  • hi, yes react-native-camera will return image uri, not real path. Are you writing this app for Android or iOS? For Android, the module I mentioned should do the trick. Unfortunately it has no support to convert uri for iOS at the moment. – Antoni4 Jul 04 '16 at 20:57
  • if you are using camera roll in conjunction with react-native-camera, you can give this a go for iOS: https://github.com/scottdixon/react-native-upload-from-camera-roll/ – Antoni4 Jul 04 '16 at 21:10
  • I need it for both actually :) - i will look into your links thank you – noa-dev Jul 05 '16 at 08:13
  • Isn't camera roll just some gallery access? Does that mean i have to save the image -> then access the gallery individually to get the path ? – noa-dev Jul 05 '16 at 08:19
  • In your case I don't think you need to use camera roll, what I meant is that for iOS you can have a look how uri conversion is done by scottdixon. Have a look here: https://github.com/scottdixon/react-native-upload-from-camera-roll/blob/master/RCTCustom.m – Antoni4 Jul 05 '16 at 09:20
  • //Create NSURL from uri NSURL *url = [[NSURL alloc] initWithString:input]; – Antoni4 Jul 05 '16 at 09:22
  • I think that's the line of code that might do the trick for iOS. You should then be able to extract path from NSURL. (I am not an objective-c programmer, so this is as far as I can guide you here) – Antoni4 Jul 05 '16 at 09:57
  • Thank you - I'll see how far I can get with it. You helped me a lot – noa-dev Jul 05 '16 at 10:01
  • Glad I could help. Please let me know if you manage to solve the issue for iOS, i'm curious how that part can be done. – Antoni4 Jul 05 '16 at 10:04
  • Do you think I have to keep that in obj-c and somehow use it in my react native enviroment or could it be done different – noa-dev Jul 06 '16 at 06:38
  • I cant tell you for sure, but most likely. I'm going to get a Mac next week, so I can look into this properly for iOS, but not sure how soon you need this. – Antoni4 Jul 06 '16 at 09:40
  • as soon as possible but I can live with next week :O – noa-dev Jul 06 '16 at 09:57
  • That new path looks like an image in base64, which is not a path :p – Antoni4 Jul 07 '16 at 10:03
  • Have a look if this will help: http://stackoverflow.com/questions/6956325/convert-nsurl-to-local-file-path and http://stackoverflow.com/questions/8082719/convert-an-nsurl-to-an-nsstring – Antoni4 Jul 07 '16 at 10:18
  • Thank you - these links seem indeed helpful although I don't know how to implement it in the code :/ – noa-dev Jul 08 '16 at 08:01