s3 bucket + expo image picker results in uploading only image name, not the image, NoSuchUpload error

570 views Asked by At

I'm using expo, expo-image-picker, amplify with s3 to store images. Whenever I try to upload the image, that happens almost successfully - I see a new file on the bucket. But all of the files are 101-170 bytes size, so they don't have any real image behind that.

Though there's a new file created in s3 bucket, when I try to upload something, as well I get this error in my console.log:

ERROR [ERROR] 10:01.882 axios-http-handler - Request failed with status code 400 ERROR [ERROR] 10:01.922 AWSS3ProviderManagedUpload - Error happened while finishing the upload. ERROR [ERROR] 10:02.234 axios-http-handler - Request failed with status code 404 WARN Possible Unhandled Promise Rejection (id: 1): NoSuchUpload: The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.

I've tried many different approaches in order to fix that. I tried uploading the image from virtual android device, from real android device, from iPhone. Nothing works.

import React, { useState, useEffect } from 'react';
import { Button, Image, View } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { v4 as uuidv4 } from 'uuid';
import { Storage } from 'aws-amplify';

export default function ImagePickerExample() {
  const [image, setImage] = useState('');

  useEffect(() => {
    (async () => {
      const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
      if (status !== 'granted') {
        alert('Sorry, we need camera roll permissions to make this work!');
      }
    })();
  }, []);

  const pickImage = async () => {
    try {
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.All,
        allowsEditing: true,
        aspect: [4, 3],
        quality: 1,
      });

      console.log(result);

      if (!result.canceled) {
        setImage(result.assets[0].uri);
      }
    } catch (error) {
      console.log('Error picking an image:', error);
    }
  };

  useEffect(() => {
    const uploadImageToStorage = async () => {
      try {
        const response = await fetch(image);
        const blob = await response.blob();
        Storage.put(`${uuidv4()}.jpg`, blob);
        console.log('Image uploaded successfully!');
      } catch (error) {
        console.log('Error uploading file:', error);
      }
    };

    if (image) {
      uploadImageToStorage();
    }
  }, [image]);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="Pick an image from camera roll" onPress={pickImage} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
    </View>
  );
}

I checked the CORS policy of my bucket to include EAT, I checked the aws-exports file has the actual bucket id in it, the region is correct as well. I tried to use many different options of imagePicker, tried almost every option in Storage.put.

1

There are 1 answers

0
ANDREI MIRONOV On

So I found what was the error for me. It took almost 20 hours of search, but try this:

  1. Make sure you have expo-cli installed. In the end that was my case.
  2. Make sure you have up to date aws-amplify installed, or if you were following youtube guides - you might have missed that behind initializing the amplify in your project.
  3. Make sure you have updated the expo AsyncStorage and expo netinfo. Those two are extremely important.
  4. The final code that worked for me is this:

import React, { useState, useEffect } from "react";
import { Button, Image, View, Platform } from "react-native";
import * as ImagePicker from "expo-image-picker";
import { Amplify, Storage } from "aws-amplify";
import awsconfig from "../../aws-exports";
Amplify.configure(awsconfig);

export default function ImagePickerExample() {
  const [image, setImage] = useState(null);

  const fetchImage = async (uri) => {
    const response = await fetch(uri);
    const blob = await response.blob();
    return blob;
  };
  const uploadFile = async (file) => {
    const img = await fetchImage(file.uri);
    return Storage.put(`my-image-filename${Math.random()}.jpg`, img, {
      level: "public",
      contentType: file.type,
      progressCallback(uploadProgress) {
        console.log(
          "PROGRESS",
          uploadProgress.loaded + "/" + uploadProgress.total
        );
      },
    })
      .then((res) => {
        Storage.get(res.key)
          .then((res) => {
            console.log("Result", res);
          })
          .catch((e) => {
            console.log(e);
          });
      })
      .catch((e) => console.log(e));
  };
  const pickImage = async () => {
    // No permissions request is necessary for launching the image library
    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.All,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    console.log(result);
    if (!result.canceled) {
      uploadFile(result.assets[0]);
      setImage(result.assets[0].uri);
    }
  };

  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
      <Button title="Pick an image from camera roll" onPress={pickImage} />
      {image && (
        <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />
      )}
    </View>
  );
}

Make sure you work correctly with that assets[0].uri and so on. Due to the latest update of expo ImagePicker package, it's no longer result.uri, it's result.assets[0].uri

Good luck everyone!